Overview

First of all, we will mention a bit about the owner of this dataset, Goodreads . Goodreads is a social cataloging website that allows individuals to freely search its database of books, annotations, and reviews.

This project aims to have an insight about different numerical parameters of the book and their relationship. Moreoever, alongside with the informaion given, we try to do a bit of web-scrapping and get another attribute (in this case, ‘Year’) in order to take our analysis further. Moreover, this would be my first milestone into Data Analysis, using all the skills and knowledges I have acquired during my summer time working with R.

This project is made possible by user Soumik on Kaggle, along with the wonderful reference from the Python notebook named Goodreads: Analysis and Recommending Books of Shivam Ralli. I would like to send my sincere thanks to both of them.

All of the datasets and scripts used will be uploaded along with this notebook.

Loading libraries and understanding the basics of this dataset

Loading libraries

First, we will load all the necessary libraries for this notebook.

library(ggplot2)
library(dplyr)

Attaching package: ‘dplyr’

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
library(RColorBrewer)
library(viridis)  
Loading required package: viridisLite
library(WVPlots)
library(naniar)
library(e1071)
library(plotrix)
library(ggcorrplot)
library(waffle)
library(extrafont) 
Registering fonts with R
library(GGally)

Attaching package: ‘GGally’

The following object is masked from ‘package:dplyr’:

    nasa
library(tidyverse)
── Attaching packages ────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.2.1 ──
✔ tibble  2.1.1     ✔ purrr   0.3.2
✔ tidyr   0.8.3     ✔ stringr 1.4.0
✔ readr   1.3.1     ✔ forcats 0.4.0
── Conflicts ───────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
library(cluster)
library(factoextra)
Welcome! Related Books: `Practical Guide To Cluster Analysis in R` at https://goo.gl/13EFCZ
library(rvest)
Loading required package: xml2

Attaching package: ‘rvest’

The following object is masked from ‘package:purrr’:

    pluck

The following object is masked from ‘package:readr’:

    guess_encoding

Understanding the basic structure of the dataset

We import and investigate the structure of the dataset.

goodreads <- read.csv('/Users/ngohoanganh/Desktop/Goodreads Kaggle project/books.csv', stringsAsFactors = FALSE)
str(goodreads)
'data.frame':   13724 obs. of  10 variables:
 $ bookID            : int  1 2 3 4 5 8 9 10 12 13 ...
 $ title             : chr  "Harry Potter and the Half-Blood Prince (Harry Potter  #6)" "Harry Potter and the Order of the Phoenix (Harry Potter  #5)" "Harry Potter and the Sorcerer's Stone (Harry Potter  #1)" "Harry Potter and the Chamber of Secrets (Harry Potter  #2)" ...
 $ authors           : chr  "J.K. Rowling-Mary GrandPré" "J.K. Rowling-Mary GrandPré" "J.K. Rowling-Mary GrandPré" "J.K. Rowling" ...
 $ average_rating    : chr  "4.56" "4.49" "4.47" "4.41" ...
 $ isbn              : chr  "0439785960" "0439358078" "0439554934" "0439554896" ...
 $ isbn13            : chr  "9780439785969" "9780439358071" "9780439554930" "9780439554893" ...
 $ language_code     : chr  "eng" "eng" "eng" "eng" ...
 $ X..num_pages      : chr  "652" "870" "320" "352" ...
 $ ratings_count     : int  1944099 1996446 5629932 6267 2149872 38872 18 27410 3602 240189 ...
 $ text_reviews_count: int  26249 27613 70390 272 33964 154 1 820 258 3954 ...
head(goodreads)
print(paste('The original goodreads book dataset has',nrow(goodreads),'rows and',ncol(goodreads),'columns.'))
[1] "The original goodreads book dataset has 13724 rows and 10 columns."

Fixing problems of the dataset

First of all, we know that this dataset has 13724 observations with 10 variables. There is one variable that is supposed to be written as “# num_pages”. However, as R cannot read special characters in column names, for the sake of convenience, we rename this variable as “num_pages”.

We obverse some problems with this dataset. At columns 4 and 8 (corresponding to variables “average_rating” and “num_page”), we can see that the observations are of type “character”, while they should be of type “integer”. We fix the problem with the function as.numeric in the following code chunk.

However, while fixing the problem above, those two columns yield some NA values. This can be due to those rows having too many commas, forcing the values that should belong to one columns move to another column. We omit these outliers for a clean dataset by using the function na.omit.

Moreover, one of the famous authors that we know, J.K Rowling, has a fairly long name of “J.K Rowling-Mary GrandPré”. After changing the author’s name, we have the dataset available for further analysis.

goodreads[goodreads=='J.K. Rowling-Mary GrandPré'] <- 'J.K. Rowling'
head(goodreads)
for(i in c(4,8)) {
  goodreads[,i] <- as.numeric(goodreads[,i])
}
NAs introduced by coercionNAs introduced by coercion
names(goodreads)[names(goodreads) == "X..num_pages"] <- "num_pages"
goodreads <- na.omit(goodreads)
str(goodreads)  
'data.frame':   13714 obs. of  10 variables:
 $ bookID            : int  1 2 3 4 5 8 9 10 12 13 ...
 $ title             : chr  "Harry Potter and the Half-Blood Prince (Harry Potter  #6)" "Harry Potter and the Order of the Phoenix (Harry Potter  #5)" "Harry Potter and the Sorcerer's Stone (Harry Potter  #1)" "Harry Potter and the Chamber of Secrets (Harry Potter  #2)" ...
 $ authors           : chr  "J.K. Rowling" "J.K. Rowling" "J.K. Rowling" "J.K. Rowling" ...
 $ average_rating    : num  4.56 4.49 4.47 4.41 4.55 4.78 3.69 4.73 4.38 4.38 ...
 $ isbn              : chr  "0439785960" "0439358078" "0439554934" "0439554896" ...
 $ isbn13            : chr  "9780439785969" "9780439358071" "9780439554930" "9780439554893" ...
 $ language_code     : chr  "eng" "eng" "eng" "eng" ...
 $ num_pages         : num  652 870 320 352 435 ...
 $ ratings_count     : int  1944099 1996446 5629932 6267 2149872 38872 18 27410 3602 240189 ...
 $ text_reviews_count: int  26249 27613 70390 272 33964 154 1 820 258 3954 ...
 - attr(*, "na.action")= 'omit' Named int  4011 4012 5688 5689 7057 7058 10603 10604 10671 10672
  ..- attr(*, "names")= chr  "4011" "4012" "5688" "5689" ...
print(paste('The final goodreads book dataset has',nrow(goodreads),'rows and',ncol(goodreads),'columns.'))
[1] "The final goodreads book dataset has 13714 rows and 10 columns."

After fixing all the errors appeared in the dataset, we start to define the variables’ names. Their description is as follow:

  • bookID: the unique ID for each book/series
  • title: the title of each book/series
  • authors: the author(s) of the particular book/series
  • average_rating: the average ratings of the particular book/series, given by Goodreads’ users
  • ISBN: old ISBN number of 10 digits, including information about a book/series, for example name, author(s), publisher, genre, etc.
  • ISBN13: new ISBN number of 13 digits, introduced in 2007
  • Language_code: 3-character language code, which indicates the language in which the book is written
  • Num_page: number of pages of each book/series
  • Ratings_count: number of ratings given by users for each book/series
  • Text_reviews_count: number of text reviews (apart from point reviews of scale 5) given by users for each book/series

Missingness of data

We investigate whether there is any data cell missing with the vis_miss plot. Since we do not plot any significant in the plot, and it shows that less than 0.1% of the data is missing, we use gg_miss_var to get the exact number of data cells missing in each column. The result is, surprisingly, there is no data misising.

vis_miss(goodreads)

gg_miss_var(goodreads)

Exploratory Data Analysis (EDA) of the dataset Goodreads

In this part, we will answer some of the interesting exploratory questions by visualizing data.

Correlation between numerical columns

First, we will see the correlation between numerical columns of this dataset by using Pearson’s R, Kendall rank and Spearman correlation coefficient.

  • Pearson’s R correlation coefficient is used to examine the strength and direction of the linear relationship between two continuous variables.
  • Kendall rank correlation coefficient (or Kendall tau’s coefficient) is a statistic used to measure the ordinal association between two measured quantities.
  • Spearman’s rank correlation coefficient (or Spearman’s rho) is a non-parametric measure of rank correlation (statistical dependence between the rankings of two variables). It assesses how well the relationship between two variables can be described using a monotonic function.
goodreads_corr <- subset(goodreads, select = c('average_rating','num_pages','ratings_count','text_reviews_count'))
goodreads_corr_pearson_round2 <- round(cor(goodreads_corr, method = "pearson", use = "pairwise.complete.obs"), 2)
data.frame(goodreads_corr_pearson_round2)
goodreads_corr_kendall_round2 <- round(cor(goodreads_corr, method = "kendall", use = "pairwise.complete.obs"), 2)
data.frame(goodreads_corr_kendall_round2)
goodreads_corr_spearman_round2 <- round(cor(goodreads_corr, method = "spearman", use = "pairwise.complete.obs"), 2)
data.frame(goodreads_corr_spearman_round2)
ggcorrplot(goodreads_corr_pearson_round2, hc.order = TRUE,
   lab = TRUE,
   title = "Pearson's R correlation matrix for Goodreads numerical variables",
   outline.col = "white",
   ggtheme = ggplot2::theme_gray,
   colors = c("#6D9EC1", "white", "#E46726"))

ggcorrplot(goodreads_corr_kendall_round2, hc.order = TRUE,
   lab = TRUE,
   title = "Kendall correlation matrix for Goodreads numerical variables",
   outline.col = "white",
   ggtheme = ggplot2::theme_gray,
   colors = c("#6D9EC1", "white", "#E46726"))

ggcorrplot(goodreads_corr_spearman_round2, hc.order = TRUE,
   lab = TRUE,
   title = "Spearman correlation matrix for Goodreads numerical variables",
   outline.col = "white",
   ggtheme = ggplot2::theme_gray,
   colors = c("#6D9EC1", "white", "#E46726"))

From the three correlation matrices above, we can see that there is only one pair of numerical variables that are strongly correlated to each other, which is ratings_count and text_reviews_count. All of the correlation coefficients of these two variables, regardless of the method used, are always greater than 0.8, which can be considered to be a strong correlation. The remaining pairs of variables show little positive correlation, with all the remaining coefficients of less than 0.2, some are even less than 0.05.

This shows an intuitive result that the more readers rate a book on a scale of 0 to 5, the more readers will leave a written text review for the same book.

Histogram plot of numerical variables

First, we will plot the histograms of the numerical/integer variables, including average_rating, num_pages, ratings_count and text_reviews_count.

ggplot(data = goodreads, aes(x = goodreads$average_rating)) + 
    geom_histogram(aes(y = ..density..),binwidth = 0.01, color='orange', fill='orange') +
    geom_density(colour="blue", lwd = 0.5, alpha=0.5) + 
    labs(title="Histogram for books' average ratings", x = "Average ratings", y = "Number of books") + 
    geom_vline(data = goodreads, xintercept = mean(goodreads$average_rating, na.rm = TRUE), color = "red", linetype = "dashed", size = 0.5)

ggplot(data = goodreads, aes(x = goodreads$num_pages)) + 
    geom_histogram(aes(y = ..density..),binwidth = 10, color='orange', fill='orange') +
    geom_density(colour="blue", lwd = 0.5, alpha=0.5) + 
    labs(title="Histogram for books' number of pages", x = "Number of pages", y = "Number of books") + 
    geom_vline(data = goodreads, xintercept = mean(goodreads$average_rating, na.rm = TRUE), color = "red", linetype = "dashed", size = 0.5)

ggplot(data = goodreads, aes(x = goodreads$ratings_count)) + 
    geom_histogram(aes(y = ..density..),binwidth = 100, color='orange', fill='orange') +
    geom_density(colour="blue", lwd = 0.5, alpha=0.5) + 
    labs(title="Histogram for ratings count of each book/series", x = "Ratings count", y = "Number of books") + 
    geom_vline(data = goodreads, xintercept = mean(goodreads$average_rating, na.rm = TRUE), color = "red", linetype = "dashed", size = 0.5)

ggplot(data = goodreads, aes(x = goodreads$text_reviews_count)) + 
    geom_histogram(aes(y = ..density..),binwidth = 50 , color='orange', fill='orange') +
    geom_density(colour="blue", lwd = 0.5, alpha=0.5) + 
    labs(title="Histogram for text reviews count for each book/series", x = "Text reviews count", y = "Number of books") + 
    geom_vline(data = goodreads, xintercept = mean(goodreads$average_rating, na.rm = TRUE), color = "red", linetype = "dashed", size = 0.5)

The histogram of average_ratings shows that most of the ratings lie between 3 and 5. The distribution of ratings between 3 and 5 seems to be familiar to a normal distribution (i.e most of the ratings focus around 4, and the density decreases upon reaching the edges). Later, we will investigate this distribution and test how close is it to the normal distribution.

With the histogram of num_pages, most of the books have less than 1000 pages, with many of them having 250-500 pages. Later, we will also zoom into this part of the histogram to have a closer look of the distribution.

The histograms of ratings_count and text_reviews_count is so much heavily skewed to the left that we can only see a straight small bar at the position 0, and the density curve decreases to 0 shortly. As such, to further investigate the distribution of these variables, we have to later take a closer look at the positions around 0.

Basic statistics of numerical variables

First, as R does not have a built in function to calculate the mode of a dataset, we proceed to define that function ourselves as follow.

# Define the function getmode in R to get the mode of data (appearance with highest frequency)
getmode <- function(v) {
   uniqv <- unique(v)
   uniqv[which.max(tabulate(match(v, uniqv)))]
}

Now, we will generate a table of basic properties of each variable.

basic_statistics_goodreads <- data.frame(matrix(NA, ncol = 13, nrow = 10))
names(basic_statistics_goodreads) <- c('Variable',
                             'Minimum score',
                             '25th percentile',
                             'Median',
                             'Mean score',
                             '75th percentile',
                             'Maximum score',
                             'Value with highest frequency (mode)',
                             'Frequency of mode',
                             'Standard Deviation',
                             'SE Mean',
                             'Skewness',
                             'Kurtosis')
for(i in c(4,8:10)) {
  basic_statistics_goodreads[i,1] <- colnames(goodreads)[i]
  basic_statistics_goodreads[i,2] <- min(goodreads[,i], na.rm = TRUE)
  basic_statistics_goodreads[i,3] <- quantile(goodreads[,i], 0.25, na.rm = TRUE)
  basic_statistics_goodreads[i,4] <- quantile(goodreads[,i], 0.5, na.rm = TRUE)
  basic_statistics_goodreads[i,5] <- mean(goodreads[,i], na.rm = TRUE)
  basic_statistics_goodreads[i,6] <- quantile(goodreads[,i], 0.75, na.rm = TRUE)
  basic_statistics_goodreads[i,7] <- max(goodreads[,i], na.rm = TRUE)
  basic_statistics_goodreads[i,8] <- getmode(na.omit(goodreads[,i]))
  basic_statistics_goodreads[i,9] <- sum(goodreads[,i] == basic_statistics_goodreads[i,8])
  basic_statistics_goodreads[i,10] <- sd(goodreads[,i], na.rm = TRUE)
  basic_statistics_goodreads[i,11] <- std.error(goodreads[,i], na.rm = TRUE)
  basic_statistics_goodreads[i,12] <- skewness(goodreads[,i], na.rm = TRUE)
  basic_statistics_goodreads[i,13] <- kurtosis(goodreads[,i], na.rm = TRUE)
}
basic_statistics_goodreads <- na.omit(basic_statistics_goodreads)
View(basic_statistics_goodreads)

First of all, we see an interesting fact that the mode of text_reviews_count is 0 (more than 900 books receive 0 text reviews), which means that books having 0 text reviews occur the most frequently in the dataset. Moreover, the mode of average_ratings is also exactly 4.0, which shows that readers are really generous with the books they are reading.

Among the 4 variables, there is only one variable (average_ratings) that has a negative skewness. This is due to the fact that the histogram of this variable skews heavily to the right, with most of the observations lie between 3 and 5. The three remaining variables has a significantly positive skewness, which means that these variables skew heavily to the left. This can be verified by looking at the histograms of these variables.

We also note that the 75th percentile of each variable, apart from average_ratings, is relatively very small comparing to the maximum, which can also explain the positive skewness of those variables.

All 4 variables have a positive kurtosis, which shows that the distribution of these variables have heavier tails than the normal distribution with the same mean and standard deviation.

From the properties witnessed above, to better understand the distribution of variables within specific ranges, we again generate histograms for two variables, ratings_count and text_reviews_count. This time, we will set the limit of the x-axis to be 100 for num_pages, 5000 for ratings_count and 500 for text_reviews_count.

ggplot(data = goodreads, aes(x = goodreads$num_pages)) + 
    geom_histogram(aes(y = ..density..),binwidth = 20, color='orange', fill='orange') +
    xlim(c(0,1000)) + 
    geom_density(colour="blue", lwd = 0.5, alpha=0.5) + 
    labs(title="Histogram for books' number of pages", x = "Number of pages", y = "Number of books") + 
    geom_vline(data = goodreads, xintercept = mean(goodreads$average_rating, na.rm = TRUE), color = "red", linetype = "dashed", size = 0.5)

ggplot(data = goodreads, aes(x = goodreads$ratings_count)) + 
    geom_histogram(aes(y = ..density..),binwidth = 10, color='orange', fill='orange') +
    xlim(c(0,5000)) + 
    geom_density(colour="blue", lwd = 0.5, alpha=0.5) + 
    labs(title="Histogram for ratings count of each book/series", x = "Ratings count", y = "Number of books") + 
    geom_vline(data = goodreads, xintercept = mean(goodreads$average_rating, na.rm = TRUE), color = "red", linetype = "dashed", size = 0.5)

ggplot(data = goodreads, aes(x = goodreads$text_reviews_count)) + 
    geom_histogram(aes(y = ..density..),binwidth = 1 , color='orange', fill='orange') +
    xlim(c(0,500)) + 
    geom_density(colour="blue", lwd = 0.5, alpha=0.5) + 
    labs(title="Histogram for text reviews count for each book/series", x = "Text reviews count", y = "Number of books") + 
    geom_vline(data = goodreads, xintercept = mean(goodreads$average_rating, na.rm = TRUE), color = "red", linetype = "dashed", size = 0.5)

After plotting the histograms of three variables again, we see that the ratings_count and text_reviews_count still skew heavily to the left. The mode of these two variables 0 and 3, respectively, which also add to the fact that most books receive very few ratings/reviews.

Considering number of pages, most books have the number of pages of around 175 - 325. Then, the number of book inversely proportional to the number of pages (i.e the number of books is decreasing with respect to their length).

Scatterplot of pairs of numerical variables in the dataset

ggpairs(with(goodreads, data.frame(average_rating, num_pages, ratings_count, text_reviews_count)))

From this scatterplot matrix, we can see that, apart from the pairs ratings_count and text_reviews_count, the remaining shows little to no correlation. All of the scatterplot shows a nearly vertical trend line (i.e there is no linear correlation between the two variables plotted).

Rating distribustion of books in the dataset

First, we divide the ratings into five different ranges:

  • Between 0 and 1
  • Between 1 and 2
  • Between 2 and 3
  • Between 3 and 4
  • Between 4 and 5

We calculate the number of books within each range, along with their percentages.

rating_distribution_goodreads <- data.frame(matrix(0, ncol = 3, nrow = 5))
names(rating_distribution_goodreads) <- c('Range',
                             'number_of_books',
                             'percentage')
for(i in c(1:5)) {
  rating_distribution_goodreads[i,1] <- paste('Between',i-1,'and',i)
}
for(i in c(1:nrow(goodreads))) {
  if (goodreads[i,4] >= 0 & goodreads[i,4] < 1) {
    rating_distribution_goodreads[1,2] = rating_distribution_goodreads[1,2] + 1 
  }
  if (goodreads[i,4] >= 1 & goodreads[i,4] < 2) {
    rating_distribution_goodreads[2,2] = rating_distribution_goodreads[2,2] + 1 
    }
  if (goodreads[i,4] >= 2 & goodreads[i,4] < 3) {
    rating_distribution_goodreads[3,2] = rating_distribution_goodreads[3,2] + 1 
    }
  if (goodreads[i,4] >= 3 & goodreads[i,4] < 4) {
    rating_distribution_goodreads[4,2] = rating_distribution_goodreads[4,2] + 1 
    }
  if (goodreads[i,4] >= 4 & goodreads[i,4] < 5) {
    rating_distribution_goodreads[5,2] = rating_distribution_goodreads[5,2] + 1 
  }
}
rating_distribution_goodreads$percentage <- round((rating_distribution_goodreads$number_of_books)/nrow(goodreads)*100,digits = 2)
rating_distribution_goodreads

Then, we will plot two different pie charts, a traditional one and a square pie chart, to observe the distribution of books within ranges.

ggplot(data=rating_distribution_goodreads, aes(x="", y=percentage,
       fill = factor(rating_distribution_goodreads[,1]), )) +
       geom_bar(width = 1, stat = "identity") + 
       ggtitle("Pie chart of percentage of Books with respect to ratings count") + 
       coord_polar(theta="y", start = 0) +
       labs(fill = factor(rating_distribution_goodreads[,1]))

rating_distribution_number <- c(`Between 0 and 1`= 0.25, `Between 1 and 2`= 0.02, `Between 2 and 3`= 0.53, `Between 3 and 4`= 55.32, `Between 4 and 5`= 43.69)
waffle(rating_distribution_number, rows=6, 
       colors=brewer.pal(5,"Set1"),
       title="Percentage of Books with respect to ratings count")

We can see that, more than half of the books are rated between 3 and 4 points, while most the remaining books are ranked between 4 and 5. There are a total of 13577 books rated between 3 and 5, which counts up to 99% of the books in the dataset.

Books with the most occurences in the dataset

book_frequency <- data.frame(table(goodreads$title), stringsAsFactors = FALSE)
names(book_frequency) <- c('title','frequency')
book_frequency <- book_frequency[order(-book_frequency$frequency),] 
head(book_frequency)
book_frequency_top20 <- book_frequency[c(1:20),]
ggplot(data=book_frequency_top20, aes(x=reorder(title, frequency),y=frequency, fill = factor(c(1:nrow(book_frequency_top20))))) +
  theme(legend.position = "none") + 
  geom_bar(stat="identity") +
  coord_flip() +
  ggtitle('20 books with highest frequency') + 
  labs(x = "Title", y = "Frequency") 

Above, we have plotted the 20 books with the most appearances in the dataset. We can see that “One Hundred Year of Solitude” and “Salem’s Lot” are the two books appearing the most, with 11 times for each book.

These books are published again and again, each time with another publisher. This shows that these are still in great demand, despite the flow of time.

Languages written in books/series

book_language <- data.frame(table(goodreads$language_code), stringsAsFactors = FALSE)
names(book_language) <- c('language','frequency')
book_language <- book_language[order(-book_language$frequency),]
book_language$percentage <- round(book_language$frequency/nrow(goodreads)*100, digits = 2)
head(book_language)
ggplot(book_language, aes(x=reorder(language,-frequency),y=frequency, fill = factor(c(1:nrow(book_language))))) +
  theme(legend.position = "none") + 
  geom_bar(stat="identity") +
  ggtitle('Languages written in books/series') + 
  labs(x = "Language", y = "Total number of books") +
  geom_text(aes(label=frequency), vjust=-0.5)

We can easily see that most of the books in this dataset is written in English. Two most used languages are English and English-US, while within 6 most commonly used languages, English and its different versions (eng-US and eng-GB) counts up to half of them. Combining all the different versions of this language, English is written in 12634 books, which is 92,12% of all the books. Apart from English, Spanish, the third commonly used language, only has 419 books, which is 3.1% of the books in this dataset.

10 Books with the highest average rating

book_average_rating <- goodreads[order(-goodreads$average_rating),]
book_average_rating_top10 <- book_average_rating[c(1:10),]
book_average_rating_top10
ggplot(data=book_average_rating_top10, aes(x=reorder(title, average_rating),y=average_rating, fill = factor(c(1:nrow(book_average_rating_top10))))) +
  theme(legend.position = "none") + 
  geom_bar(stat="identity") +
  coord_flip() +
  scale_fill_brewer(palette="Paired") + 
  ggtitle('Books/series with highest average rating') + 
  labs(x = "Book/series", y = "Average rating") +
  geom_text(
    aes(label = average_rating), colour = "black",
    hjust = -0.1, size = 2,
    position = position_dodge(width = 1),
    inherit.aes = TRUE
  )

author_highest_book_average_rating <- data.frame(table(book_average_rating_top10$authors))
names(author_highest_book_average_rating) <- c('author', 'number_of_books')
author_highest_book_average_rating <- author_highest_book_average_rating[order(-author_highest_book_average_rating$number_of_books),]
author_highest_book_average_rating

Surprisingly, all 10 of the highest rating books have the maximum rating, 5. As a result, we will investigate all the books that have the rating of 5 in this dataset.

books_rating_5 <- goodreads[goodreads$average_rating == 5,]
books_rating_5 <- books_rating_5[order(-books_rating_5$ratings_count),]
books_rating_5 
print(paste('There are',nrow(books_rating_5),'books that receive an average rating of 5.'))
[1] "There are 28 books that receive an average rating of 5."

We can easily see that all of the books with average ratings of 5 receive very few ratings. The maximum ratings count among these books are 5. Specifically, there are 4 books that receive 0 ratings, and still receive an average rating of 5, which is not supposed to happen. Moreover, 23/28 books in the list receive 0 text reviews. We can not consider these books to be the ones with highest average rating, as the ratings count is too low.

Instead, we propose the following method: We only consider the average rating of books with more than 1000 ratings.

book_1000_ratings_count <- goodreads[goodreads$ratings_count >= 1000,]
book_1000_ratings_count <- book_1000_ratings_count[order(-book_1000_ratings_count$average_rating),]
book_1000_ratings_count
book_1000_ratings_count_top10 <- book_1000_ratings_count[c(1:10),]
book_1000_ratings_count_top10
ggplot(data=book_1000_ratings_count_top10, aes(x=reorder(title, average_rating),y=average_rating, fill = factor(c(1:nrow(book_1000_ratings_count_top10))))) +
  theme(legend.position = "none") + 
  geom_bar(stat="identity") +
  coord_flip() +
  scale_fill_brewer(palette="Paired") + 
  ggtitle('Books/series with highest average rating (ratings count >= 1000)') + 
  labs(x = "Book/series", y = "Average rating") +
  geom_text(
    aes(label = average_rating), colour = "black",
    hjust = -0.1, size = 2,
    position = position_dodge(width = 1),
    inherit.aes = TRUE
  )

author_book_1000_ratings_count <- data.frame(table(book_1000_ratings_count_top10$authors))
names(author_book_1000_ratings_count) <- c('author', 'number_of_books')
author_book_1000_ratings_count <- author_book_1000_ratings_count[order(-author_book_1000_ratings_count$number_of_books),]
author_book_1000_ratings_count

We can see that, there is only 6059 books that have the ratings count of more than or equal to 1000, which accounts for 44.18% of all the books in the dataset.

Within these 6059 books, the highest has the average rating of 4.82, while the lowest rating among the top 10 books is 4.7.

Among those 10 books, half of them are written by Bill Watterson, two are from J.K. Rowling, one from Patrick O’Brian, one from Joyce Meyer and one from anonymous. We can easily see that Bill Watterson has written books that receive very high ratings from a large number of readers. Two among the top 10 from J.K Rowling are both sets of Harry Potter (one set of #1-5 and one set of #1-6).

Another interesting fact is that all books in top 10 are written in English, with one specifically written in en-US.

10 longest books in the dataset

book_num_pages <- goodreads[order(-goodreads$num_pages),]
book_num_pages_top10 <- book_num_pages[c(1:10),]
book_num_pages_top10
ggplot(data=book_num_pages_top10, aes(x=reorder(title, num_pages),y=num_pages, fill = factor(c(1:nrow(book_num_pages_top10))))) +
  theme(legend.position = "none") + 
  geom_bar(stat="identity") +
  coord_flip() +
  scale_fill_brewer(palette="Paired") + 
  ggtitle('Books/series with highest number of pages') + 
  labs(x = "Book/series", y = "Number of pages") +
  geom_text(
    aes(label = num_pages), colour = "black",
    hjust = -0.1, size = 2,
    position = position_dodge(width = 1),
    inherit.aes = TRUE
  )

author_book_num_pages <- data.frame(table(book_num_pages_top10$authors))
names(author_book_num_pages) <- c('author', 'number_of_books')
author_book_num_pages <- author_book_num_pages[order(-author_book_num_pages$number_of_books),]
author_book_num_pages

We can easily see that, all of the 10 longest in this dataset are all series. Among them, “The Complet Aubrey/Maturin Novels” stands out from the crowd, with its length of nearly 3 times the length of the 10th longest series, “Civil War: a Narrative”. Moreover, “The Norton Anthology of English Literature” has 3 of their volumns presenting in the top 10 (Vols A-C, Vol 2 and Vol 1). However, their appearances are not consistent: Vols A-C and Vol 1 have the same name, but their number of pages are different.

10 Books with the highest number of ratings count

book_ratings_count <- goodreads[order(-goodreads$ratings_count),]
book_ratings_count_top10 <- book_ratings_count[c(1:10),]
book_ratings_count_top10
ggplot(data=book_ratings_count_top10, aes(x=reorder(title, ratings_count),y=ratings_count, fill = factor(c(1:nrow(book_ratings_count_top10))))) +
  theme(legend.position = "none") + 
  geom_bar(stat="identity") +
  coord_flip() +
  scale_fill_brewer(palette="Paired") + 
  ggtitle('Books/series with highest number of ratings count') + 
  labs(x = "Book/series", y = "Number of ratings") +
  geom_text(
    aes(label = ratings_count), colour = "black",
    hjust = -0.1, size = 2,
    position = position_dodge(width = 1),
    inherit.aes = TRUE
  )

author_most_book_ratings_count <- data.frame(table(book_ratings_count_top10$authors))
names(author_most_book_ratings_count) <- c('author', 'number_of_books')
author_most_book_ratings_count <- author_most_book_ratings_count[order(-author_most_book_ratings_count$number_of_books),]
author_most_book_ratings_count

There is a huge gap between the first two most rated books and the remainings. Specifically speaking, there is a 2 million rating gap between Twilight #1 - the second most rated book and “The Hobbit or Three and Back Again” - the third most rated book. Even between the first position, “Harry Potter and the Sorcerer’s Stone” and the second one, there is also a significant gap of about 1.3 million ratings. However, between the third and the 10th position, the gap is just about 350.000 ratings.

About the authors’ list of top 10 books, we can easily see that J.K Rowling and her Harry Potter series are dominating the ratings count, with 4 of her books in the series making their ways to the top 10. - Harry Potter and the Sorcerer’s Stone (Harry Potter #1) at first place - Harry Potter and the Prisoner of Azkaban (Harry Potter #3) at sixth place - Harry Potter and the Chamber of Secrets (Harry Potter #2) at seventh place - Harry Potter and the Order of the Phoenix (Harry Potter #5) at tenth place

Large number of ratings count, alongside with high average rating (all over 4.4), these definitely shows how successful this series have been.

10 Books with the highest number of ratings count

book_text_reviews_count <- goodreads[order(-goodreads$text_reviews_count),]
book_text_reviews_count_top10 <- book_text_reviews_count[c(1:10),]
book_text_reviews_count_top10
ggplot(data=book_text_reviews_count_top10, aes(x=reorder(title, text_reviews_count),y=text_reviews_count, fill = factor(c(1:nrow(book_text_reviews_count_top10))))) +
  theme(legend.position = "none") + 
  geom_bar(stat="identity") +
  coord_flip() +
  scale_fill_brewer(palette="Paired") + 
  ggtitle('Books/series with highest number of text reviews') + 
  labs(x = "Book/series", y = "Number of text reviews") +
  geom_text(
    aes(label = text_reviews_count), colour = "black",
    hjust = -0.1, size = 2,
    position = position_dodge(width = 1),
    inherit.aes = TRUE
  )

author_most_book_text_reviews_count <- data.frame(table(book_text_reviews_count_top10$authors))
names(author_most_book_text_reviews_count) <- c('author', 'number_of_books')
author_most_book_text_reviews_count <- author_most_book_text_reviews_count[order(-author_most_book_text_reviews_count$number_of_books),]
author_most_book_text_reviews_count

Once again, there is a significant gap between the first four positions; after that, the gap is getting much narrower.

Moreover, this time, the author’s list of these books are more widely spread, with 10 different authors, each having one of their books in the top 10 list.

We have seen some familiar names that appear in both top 10 of ratings count and text reviews count. The question is, how many of them are there, and what is the difference between their performance in these two criteria?

Common books of the two top 10 of ratings count and text reviews count

common_top10 <- intersect(book_text_reviews_count_top10, book_ratings_count_top10)  
common_top10

Although there is a very strong positive correlation between the two variables, there are only two books that belong to both top 10 of these two criteria. They are:

  • Twilight (Twilight #1)
  • Harry Potter and the Sorcerer’s Stone (Harry Potter #1)

These two books are performing extremely well in both criteria, and so far the leading books in this dataset, considering the number of ratings/reviews:

  • With Twilight (Twilight #1): Top 2 in ratings count, Top 1 in text reviews count
  • With Harry Potter and the Sorcerer’s Stone (Harry Potter #1): Top 1 in ratings count, Top 3 in text reviews count.
author_most_books <- data.frame(table(goodreads$authors), stringsAsFactors = FALSE)
names(author_most_books) <- c('author','total_number_of_books')
author_most_books <- author_most_books[order(-author_most_books$total_number_of_books),] 
head(author_most_books)
author_most_books_top10 <- author_most_books[c(1:10),]
ggplot(data=author_most_books_top10, aes(x=reorder(author, total_number_of_books),y=total_number_of_books, fill = factor(c(1:nrow(author_most_books_top10))))) +
  theme(legend.position = "none") + 
  geom_bar(stat="identity") +
  coord_flip() +
  scale_fill_brewer(palette="Set3") + 
  ggtitle('Author with greatest number of books appeared') + 
  labs(x = "Author", y = "Total number of books") +
  geom_text(
    aes(label = total_number_of_books), colour = "black",
    hjust = -0.5, size = 3,
    position = position_dodge(width = 1),
    inherit.aes = TRUE
  )

author_most_books_top10

Once again, the first two authors creates a huge gap with the remaining. Both Agatha Christie and Stephen King have more than 65 books, which is at least 18 books more than the third author, Orson Scott Card.

What are the authors with the greatest number of highly-ranked books?

The definition of “highly-ranked books” in this context is relative. As such, we consider some of the following cases. We define a book to be highly-ranked if its average ranking is greater than:

  • 4.0
  • 4.3
  • 4.5
print(paste('There are',sum(goodreads$average_rating >= 4.0),'books that have a rating of greater than or equal to 4.0, which accounts for',round(sum(goodreads$average_rating >= 4.0)/nrow(goodreads)*100, digits = 2),'percent of the books in this dataset.'))
[1] "There are 6019 books that have a rating of greater than or equal to 4.0, which accounts for 43.89 percent of the books in this dataset."
print(paste('There are',sum(goodreads$average_rating >= 4.3),'books that have a rating of greater than or equal to 4.3, which accounts for',round(sum(goodreads$average_rating >= 4.3)/nrow(goodreads)*100, digits = 2),'percent of the books in this dataset.'))
[1] "There are 1301 books that have a rating of greater than or equal to 4.3, which accounts for 9.49 percent of the books in this dataset."
print(paste('There are',sum(goodreads$average_rating >= 4.5),'books that have a rating of greater than or equal to 4.5, which accounts for',round(sum(goodreads$average_rating >= 4.5)/nrow(goodreads)*100, digits = 2),'percent of the books in this dataset.'))
[1] "There are 271 books that have a rating of greater than or equal to 4.5, which accounts for 1.98 percent of the books in this dataset."
author_most_book_high_ratings <- goodreads[goodreads$average_rating >= 4.0,]
author_most_books_high_ratings <- data.frame(table(author_most_book_high_ratings$authors), stringsAsFactors = FALSE)
names(author_most_books_high_ratings) <- c('author','number_of_books_>=_4.0')
author_most_books_high_ratings <- author_most_books_high_ratings[order(-author_most_books_high_ratings$`number_of_books_>=_4.0`),] 
author_most_book_high_ratings_2 <- goodreads[goodreads$average_rating >= 4.3,]
author_most_books_high_ratings_2 <- data.frame(table(author_most_book_high_ratings_2$authors), stringsAsFactors = FALSE)
names(author_most_books_high_ratings_2) <- c('author','number_of_books_>=_4.3')
author_most_books_high_ratings_2 <- author_most_books_high_ratings_2[order(-author_most_books_high_ratings_2$`number_of_books_>=_4.3`),] 
author_most_book_high_ratings_3 <- goodreads[goodreads$average_rating >= 4.5,]
author_most_books_high_ratings_3 <- data.frame(table(author_most_book_high_ratings_3$authors), stringsAsFactors = FALSE)
names(author_most_books_high_ratings_3) <- c('author','number_of_books_>=_4.5')
author_most_books_high_ratings_3 <- author_most_books_high_ratings_3[order(-author_most_books_high_ratings_3$`number_of_books_>=_4.5`),] 
author_most_books_high_ratings_top10 <- author_most_books_high_ratings[c(1:10),]
ggplot(data=author_most_books_high_ratings_top10, aes(x=reorder(author, `number_of_books_>=_4.0`),y=`number_of_books_>=_4.0`, fill = factor(c(1:nrow(author_most_books_high_ratings_top10))))) +
  theme(legend.position = "none") + 
  geom_bar(stat="identity") +
  coord_flip() +
  scale_fill_brewer(palette="Set3") + 
  ggtitle('Author with greatest number of books ranked >= 4.0') + 
  labs(x = "Author", y = "Total number of highly books ranked >= 4.0") +
  geom_text(
    aes(label = `number_of_books_>=_4.0`), colour = "black",
    hjust = -0.5, size = 3,
    position = position_dodge(width = 1),
    inherit.aes = TRUE
  )

author_most_books_high_ratings_top10_2 <- author_most_books_high_ratings_2[c(1:10),]
ggplot(data=author_most_books_high_ratings_top10_2, aes(x=reorder(author, `number_of_books_>=_4.3`),y=`number_of_books_>=_4.3`, fill = factor(c(1:nrow(author_most_books_high_ratings_top10_2))))) +
  theme(legend.position = "none") + 
  geom_bar(stat="identity") +
  coord_flip() +
  scale_fill_brewer(palette="Set3") + 
  ggtitle('Author with greatest number of books ranked >= 4.3') + 
  labs(x = "Author", y = "Total number of books ranked >= 4.3") +
  geom_text(
    aes(label = `number_of_books_>=_4.3`), colour = "black",
    hjust = -0.5, size = 3,
    position = position_dodge(width = 1),
    inherit.aes = TRUE
  )

author_most_books_high_ratings_top10_3 <- author_most_books_high_ratings_3[c(1:10),]
ggplot(data=author_most_books_high_ratings_top10_3, aes(x=reorder(author, `number_of_books_>=_4.5`),y=`number_of_books_>=_4.5`, fill = factor(c(1:nrow(author_most_books_high_ratings_top10_3))))) +
  theme(legend.position = "none") + 
  geom_bar(stat="identity") +
  coord_flip() +
  scale_fill_brewer(palette="Set3") + 
  ggtitle('Author with greatest number of books ranked >= 4.5') + 
  labs(x = "Author", y = "Total number of books ranked >= 4.5") +
  geom_text(
    aes(label = `number_of_books_>=_4.5`), colour = "black",
    hjust = -0.5, size = 3,
    position = position_dodge(width = 1),
    inherit.aes = TRUE
  )

list_authors_top10s <- Reduce(function(x,y) merge(x,y,by="author",all=TRUE) ,list(author_most_books_high_ratings_top10,author_most_books_high_ratings_top10_2,author_most_books_high_ratings_top10_3))
list_authors_top10s

From the information gathered above, we can subjectively define the quality of a book based on its rating as follow:

  • Between 4.0 and 4.3: Average/Fair
  • Between 4.3 and 4.5: Good
  • Above 4.5: Excellent

There are a total of 21 authors that appear in the three top 10 of authors who has the greatest number of books (with 3 different cutoffs). From the table above, we have the following observations:

  • There are some authors who write a lot of above average/fair books, but only some or none of them are good or above. This means that most of their books are ranked between 4.0 and 4.3. Some authors in this group are Janet Evanovich, Mercedes Lackey, Stephen King, Rumiko Takahashi, Terry Pratchett, Agatha Christie. They are in top 10 with the 4.0 cutoff, but can not make it to top 10 with higher cutoffs.
    • For example, Rumiko Takahashi has 44 books ranked 4.0 or above, but he has fewer books ranked over 4.3 than Hiromu Arakawa (8 books).
  • On the other hand, there are some authors who write fewer books, but most of their books are ranked fairly high. The two most noble names for this group are ill Watterson and J.K.Rowling.
    • Bill Watterson are not in top 10 with books over 4.0, but he has 16 books with ratings greater than or equal to 4.3, 15 of which has ratings greater than or equal to 4.5
    • J.K. Rowling has 31 books above average, all of them having ratings greater than or equal to 4.3, and 10 of them has ratings greater than or equal to 4.5
    • Hiromu Arakawa - Akira Watanabe has only 12 books whose ratings are greater than or equal to 4.3, but 11 of them have ratings greater than or equal to 4.5
  • There are some authors who write very few books (i.e does not appear in top 10 of cutoffs 4.0 and 4.3), but most/all of their books are very highly rated (greater than or equal to 4.5).
    • Hayao Miyazaki with 6 books rated greater than or equal to 4.5
    • Hiromu Arakawa and Jane Austen with 4 books rated greater than or equal to 4.5

We can also see that, the distance between 4.0 and 4.3 is only 0.3, but the amount of books rated has decreased 5 times.

Applying K-means algorithm to cluster datasets

K-means clustering is the most commonly used unsupervised machine learning algorithm for partitioning a given data set into a set of k groups (i.e. k clusters), where k represents the number of groups pre-specified by the analyst.

First, we will build a model to cluster this dataset based on two variables: average_rating and ratings_count.

To determine the optimal number o clusters, we use the two following popular methods:

goodreads_kmeans1 <- goodreads[,c('average_rating','ratings_count')]
set.seed(123)
fviz_nbclust(goodreads_kmeans1, kmeans, method = "wss")

fviz_nbclust(goodreads_kmeans1, kmeans, method = "silhouette")

From both of the two approaches above, the optimal number of clusters seem to be at 5. As a result, we will perform an analysis and extract the results with 5 clusters.

set.seed(123)
final_kmeans1 <- kmeans(goodreads_kmeans1, 5, nstart = 25)
print(final_kmeans1)
K-means clustering with 5 clusters of sizes 23, 13257, 357, 75, 2

Cluster means:
  average_rating ratings_count
1       4.100000   1760033.261
2       3.927476      5645.072
3       4.015490    191223.644
4       4.027733    667374.147
5       4.030000   4998636.500

Clustering vector:
   1    2    3    4    5    6    7    8    9   10   11   12   13   14   15   16   17   18   19   20   21   22   23   24   25   26   27   28   29 
   1    1    5    2    1    2    2    2    2    3    2    2    2    3    2    2    2    2    2    2    2    2    2    2    2    1    2    2    2 
  30   31   32   33   34   35   36   37   38   39   40   41   42   43   44   45   46   47   48   49   50   51   52   53   54   55   56   57   58 
   2    2    2    3    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2 
  59   60   61   62   63   64   65   66   67   68   69   70   71   72   73   74   75   76   77   78   79   80   81   82   83   84   85   86   87 
   2    2    2    2    2    2    3    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2 
  88   89   90   91   92   93   94   95   96   97   98   99  100  101  102  103  104  105  106  107  108  109  110  111  112  113  114  115  116 
   2    2    2    2    2    2    2    2    3    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2 
 117  118  119  120  121  122  123  124  125  126  127  128  129  130  131  132  133  134  135  136  137  138  139  140  141  142  143  144  145 
   2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2 
 146  147  148  149  150  151  152  153  154  155  156  157  158  159  160  161  162  163  164  165  166  167  168  169  170  171  172  173  174 
   3    2    2    2    2    2    2    2    4    2    2    2    2    2    2    2    3    2    2    2    3    2    2    2    2    2    2    2    2 
 175  176  177  178  179  180  181  182  183  184  185  186  187  188  189  190  191  192  193  194  195  196  197  198  199  200  201  202  203 
   2    2    3    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2 
 204  205  206  207  208  209  210  211  212  213  214  215  216  217  218  219  220  221  222  223  224  225  226  227  228  229  230  231  232 
   2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2 
 233  234  235  236  237  238  239  240  241  242  243  244  245  246  247  248  249  250  251  252  253  254  255  256  257  258  259  260  261 
   2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    3    2    2    2    2    2    3    2    2    3    2    2 
 262  263  264  265  266  267  268  269  270  271  272  273  274  275  276  277  278  279  280  281  282  283  284  285  286  287  288  289  290 
   2    3    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2 
 291  292  293  294  295  296  297  298  299  300  301  302  303  304  305  306  307  308  309  310  311  312  313  314  315  316  317  318  319 
   2    2    2    2    2    2    2    2    2    2    2    2    2    2    3    2    2    2    2    2    2    2    2    1    2    2    2    3    2 
 320  321  322  323  324  325  326  327  328  329  330  331  332  333  334  335  336  337  338  339  340  341  342  343  344  345  346  347  348 
   2    2    2    2    2    1    2    2    3    2    3    1    2    2    2    2    2    2    2    2    2    1    2    2    1    2    2    2    4 
 349  350  351  352  353  354  355  356  357  358  359  360  361  362  363  364  365  366  367  368  369  370  371  372  373  374  375  376  377 
   2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    3    2    2    2    3    2    2    2    3    2    3    2    2    2 
 378  379  380  381  382  383  384  385  386  387  388  389  390  391  392  393  394  395  396  397  398  399  400  401  402  403  404  405  406 
   2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    4    2    2    2    2    2    2    2    2    3    2    2    3 
 407  408  409  410  411  412  413  414  415  416  417  418  419  420  421  422  423  424  425  426  427  428  429  430  431  432  433  434  435 
   2    2    2    2    2    2    2    3    2    2    2    2    2    3    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2 
 436  437  438  439  440  441  442  443  444  445  446  447  448  449  450  451  452  453  454  455  456  457  458  459  460  461  462  463  464 
   2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    3    2    2    2    2    2    2 
 465  466  467  468  469  470  471  472  473  474  475  476  477  478  479  480  481  482  483  484  485  486  487  488  489  490  491  492  493 
   4    2    2    2    2    2    2    2    2    2    2    2    2    4    2    2    2    2    2    2    2    2    3    3    2    2    2    2    2 
 494  495  496  497  498  499  500  501  502  503  504  505  506  507  508  509  510  511  512  513  514  515  516  517  518  519  520  521  522 
   2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2 
 523  524  525  526  527  528  529  530  531  532  533  534  535  536  537  538  539  540  541  542  543  544  545  546  547  548  549  550  551 
   2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    3    2    2 
 552  553  554  555  556  557  558  559  560  561  562  563  564  565  566  567  568  569  570  571  572  573  574  575  576  577  578  579  580 
   2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    4    4    2    3    2    3    2    2 
 581  582  583  584  585  586  587  588  589  590  591  592  593  594  595  596  597  598  599  600  601  602  603  604  605  606  607  608  609 
   2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2 
 610  611  612  613  614  615  616  617  618  619  620  621  622  623  624  625  626  627  628  629  630  631  632  633  634  635  636  637  638 
   2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    3    2    4    2    2 
 639  640  641  642  643  644  645  646  647  648  649  650  651  652  653  654  655  656  657  658  659  660  661  662  663  664  665  666  667 
   2    2    3    2    2    3    2    2    2    2    2    2    2    2    2    2    2    2    2    3    2    2    2    2    2    2    1    2    2 
 668  669  670  671  672  673  674  675  676  677  678  679  680  681  682  683  684  685  686  687  688  689  690  691  692  693  694  695  696 
   2    2    2    2    4    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2 
 697  698  699  700  701  702  703  704  705  706  707  708  709  710  711  712  713  714  715  716  717  718  719  720  721  722  723  724  725 
   2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2 
 726  727  728  729  730  731  732  733  734  735  736  737  738  739  740  741  742  743  744  745  746  747  748  749  750  751  752  753  754 
   2    2    2    2    2    2    2    2    2    2    2    2    2    3    2    2    2    2    2    2    2    2    2    2    2    2    2    2    3 
 755  756  757  758  759  760  761  762  763  764  765  766  767  768  769  770  771  772  773  774  775  776  777  778  779  780  781  782  783 
   2    4    2    2    2    2    3    2    2    2    2    2    2    2    4    3    2    3    2    2    2    2    2    2    2    2    2    2    2 
 784  785  786  787  788  789  790  791  792  793  794  795  796  797  798  799  800  801  802  803  804  805  806  807  808  809  810  811  812 
   2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2 
 813  814  815  816  817  818  819  820  821  822  823  824  825  826  827  828  829  830  831  832  833  834  835  836  837  838  839  840  841 
   2    2    2    2    2    2    2    2    2    2    2    3    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2 
 842  843  844  845  846  847  848  849  850  851  852  853  854  855  856  857  858  859  860  861  862  863  864  865  866  867  868  869  870 
   2    2    2    2    2    2    2    3    2    2    2    2    2    2    2    2    2    2    2    3    2    2    2    2    2    2    2    2    2 
 871  872  873  874  875  876  877  878  879  880  881  882  883  884  885  886  887  888  889  890  891  892  893  894  895  896  897  898  899 
   2    2    2    2    3    2    2    2    2    2    2    2    2    2    2    2    2    4    2    2    2    4    2    2    2    2    2    2    2 
 900  901  902  903  904  905  906  907  908  909  910  911  912  913  914  915  916  917  918  919  920  921  922  923  924  925  926  927  928 
   2    2    2    2    2    2    2    2    2    2    2    3    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2 
 929  930  931  932  933  934  935  936  937  938  939  940  941  942  943  944  945  946  947  948  949  950  951  952  953  954  955  956  957 
   2    3    2    2    2    2    2    2    2    3    2    2    2    2    3    2    2    2    2    2    2    2    2    2    2    2    2    2    2 
 958  959  960  961  962  963  964  965  966  967  968  969  970  971  972  973  974  975  976  977  978  979  980  981  982  983  984  985  986 
   2    2    2    2    3    2    2    2    2    2    2    2    2    2    4    2    2    2    2    2    2    2    2    2    3    2    2    2    2 
 987  988  989  990  991  992  993  994  995  996  997  998  999 1000 
   2    2    2    2    2    2    2    2    2    2    2    2    2    2 
 [ reached getOption("max.print") -- omitted 12714 entries ]

Within cluster sum of squares by cluster:
[1] 2.834358e+12 2.385364e+12 2.431455e+12 2.747742e+12 7.970680e+11
 (between_SS / total_SS =  93.6 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss" "betweenss"    "size"         "iter"         "ifault"      
str(final_kmeans1)
List of 9
 $ cluster     : Named int [1:13714] 1 1 5 2 1 2 2 2 2 3 ...
  ..- attr(*, "names")= chr [1:13714] "1" "2" "3" "4" ...
 $ centers     : num [1:5, 1:2] 4.1 3.93 4.02 4.03 4.03 ...
  ..- attr(*, "dimnames")=List of 2
  .. ..$ : chr [1:5] "1" "2" "3" "4" ...
  .. ..$ : chr [1:2] "average_rating" "ratings_count"
 $ totss       : num 1.75e+14
 $ withinss    : num [1:5] 2.83e+12 2.39e+12 2.43e+12 2.75e+12 7.97e+11
 $ tot.withinss: num 1.12e+13
 $ betweenss   : num 1.64e+14
 $ size        : int [1:5] 23 13257 357 75 2
 $ iter        : int 6
 $ ifault      : int 0
 - attr(*, "class")= chr "kmeans"
fviz_cluster(final_kmeans1, data = goodreads_kmeans1)

We can see that the data are clearly classified into 5 different clusters. These clusters can be ordered by ratings count (i.e, the mean ratings count of these clusters can be ordered in a increasing order with clear differences).

As the rating count decreases, the average rating are more widely spread (i.e the average rating rate is higher), which means that the average rating is neither concentrated nor correct.

We also can extract the clusters and add to our initial data for futher analysis at the cluster level:

goodreads_kmeans1_result <- goodreads %>%
                      mutate(Cluster = final_kmeans1$cluster)
goodreads_kmeans1_result

Now, we will try to apply K-means algorithm for all of the numerical variables of this dataset.

goodreads_kmeans2 <- goodreads[,c('average_rating','num_pages','ratings_count','text_reviews_count')]
set.seed(123)
fviz_nbclust(goodreads_kmeans2, kmeans, method = "wss")

fviz_nbclust(goodreads_kmeans2, kmeans, method = "silhouette")

After considering the elbow plot and sihouette plot, we once again come up with the optimal number of clusters of 5. As a result, we will again cluster our dataset into 5 groups.

set.seed(123)
final_kmeans2 <- kmeans(goodreads_kmeans2, 5, nstart = 25)
print(final_kmeans2)
K-means clustering with 5 clusters of sizes 2, 75, 13257, 23, 357

Cluster means:
  average_rating num_pages ratings_count text_reviews_count
1       4.030000  409.0000   4998636.500         82004.5000
2       4.027733  395.6133    667374.147         15690.9467
3       3.927476  340.7500      5645.072           239.9849
4       4.100000  409.2174   1760033.261         32802.3478
5       4.015490  387.9188    191223.644          5718.3950

Clustering vector:
   1    2    3    4    5    6    7    8    9   10   11   12   13   14   15   16   17   18   19   20   21   22   23   24   25   26   27   28   29 
   4    4    1    3    4    3    3    3    3    5    3    3    3    5    3    3    3    3    3    3    3    3    3    3    3    4    3    3    3 
  30   31   32   33   34   35   36   37   38   39   40   41   42   43   44   45   46   47   48   49   50   51   52   53   54   55   56   57   58 
   3    3    3    5    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3 
  59   60   61   62   63   64   65   66   67   68   69   70   71   72   73   74   75   76   77   78   79   80   81   82   83   84   85   86   87 
   3    3    3    3    3    3    5    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3 
  88   89   90   91   92   93   94   95   96   97   98   99  100  101  102  103  104  105  106  107  108  109  110  111  112  113  114  115  116 
   3    3    3    3    3    3    3    3    5    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3 
 117  118  119  120  121  122  123  124  125  126  127  128  129  130  131  132  133  134  135  136  137  138  139  140  141  142  143  144  145 
   3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3 
 146  147  148  149  150  151  152  153  154  155  156  157  158  159  160  161  162  163  164  165  166  167  168  169  170  171  172  173  174 
   5    3    3    3    3    3    3    3    2    3    3    3    3    3    3    3    5    3    3    3    5    3    3    3    3    3    3    3    3 
 175  176  177  178  179  180  181  182  183  184  185  186  187  188  189  190  191  192  193  194  195  196  197  198  199  200  201  202  203 
   3    3    5    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3 
 204  205  206  207  208  209  210  211  212  213  214  215  216  217  218  219  220  221  222  223  224  225  226  227  228  229  230  231  232 
   3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3 
 233  234  235  236  237  238  239  240  241  242  243  244  245  246  247  248  249  250  251  252  253  254  255  256  257  258  259  260  261 
   3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    5    3    3    3    3    3    5    3    3    5    3    3 
 262  263  264  265  266  267  268  269  270  271  272  273  274  275  276  277  278  279  280  281  282  283  284  285  286  287  288  289  290 
   3    5    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3 
 291  292  293  294  295  296  297  298  299  300  301  302  303  304  305  306  307  308  309  310  311  312  313  314  315  316  317  318  319 
   3    3    3    3    3    3    3    3    3    3    3    3    3    3    5    3    3    3    3    3    3    3    3    4    3    3    3    5    3 
 320  321  322  323  324  325  326  327  328  329  330  331  332  333  334  335  336  337  338  339  340  341  342  343  344  345  346  347  348 
   3    3    3    3    3    4    3    3    5    3    5    4    3    3    3    3    3    3    3    3    3    4    3    3    4    3    3    3    2 
 349  350  351  352  353  354  355  356  357  358  359  360  361  362  363  364  365  366  367  368  369  370  371  372  373  374  375  376  377 
   3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    5    3    3    3    5    3    3    3    5    3    5    3    3    3 
 378  379  380  381  382  383  384  385  386  387  388  389  390  391  392  393  394  395  396  397  398  399  400  401  402  403  404  405  406 
   3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    2    3    3    3    3    3    3    3    3    5    3    3    5 
 407  408  409  410  411  412  413  414  415  416  417  418  419  420  421  422  423  424  425  426  427  428  429  430  431  432  433  434  435 
   3    3    3    3    3    3    3    5    3    3    3    3    3    5    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3 
 436  437  438  439  440  441  442  443  444  445  446  447  448  449  450  451  452  453  454  455  456  457  458  459  460  461  462  463  464 
   3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    5    3    3    3    3    3    3 
 465  466  467  468  469  470  471  472  473  474  475  476  477  478  479  480  481  482  483  484  485  486  487  488  489  490  491  492  493 
   2    3    3    3    3    3    3    3    3    3    3    3    3    2    3    3    3    3    3    3    3    3    5    5    3    3    3    3    3 
 494  495  496  497  498  499  500  501  502  503  504  505  506  507  508  509  510  511  512  513  514  515  516  517  518  519  520  521  522 
   3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3 
 523  524  525  526  527  528  529  530  531  532  533  534  535  536  537  538  539  540  541  542  543  544  545  546  547  548  549  550  551 
   3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    5    3    3 
 552  553  554  555  556  557  558  559  560  561  562  563  564  565  566  567  568  569  570  571  572  573  574  575  576  577  578  579  580 
   3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    2    2    3    5    3    5    3    3 
 581  582  583  584  585  586  587  588  589  590  591  592  593  594  595  596  597  598  599  600  601  602  603  604  605  606  607  608  609 
   3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3 
 610  611  612  613  614  615  616  617  618  619  620  621  622  623  624  625  626  627  628  629  630  631  632  633  634  635  636  637  638 
   3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    5    3    2    3    3 
 639  640  641  642  643  644  645  646  647  648  649  650  651  652  653  654  655  656  657  658  659  660  661  662  663  664  665  666  667 
   3    3    5    3    3    5    3    3    3    3    3    3    3    3    3    3    3    3    3    5    3    3    3    3    3    3    4    3    3 
 668  669  670  671  672  673  674  675  676  677  678  679  680  681  682  683  684  685  686  687  688  689  690  691  692  693  694  695  696 
   3    3    3    3    2    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3 
 697  698  699  700  701  702  703  704  705  706  707  708  709  710  711  712  713  714  715  716  717  718  719  720  721  722  723  724  725 
   3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3 
 726  727  728  729  730  731  732  733  734  735  736  737  738  739  740  741  742  743  744  745  746  747  748  749  750  751  752  753  754 
   3    3    3    3    3    3    3    3    3    3    3    3    3    5    3    3    3    3    3    3    3    3    3    3    3    3    3    3    5 
 755  756  757  758  759  760  761  762  763  764  765  766  767  768  769  770  771  772  773  774  775  776  777  778  779  780  781  782  783 
   3    2    3    3    3    3    5    3    3    3    3    3    3    3    2    5    3    5    3    3    3    3    3    3    3    3    3    3    3 
 784  785  786  787  788  789  790  791  792  793  794  795  796  797  798  799  800  801  802  803  804  805  806  807  808  809  810  811  812 
   3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3 
 813  814  815  816  817  818  819  820  821  822  823  824  825  826  827  828  829  830  831  832  833  834  835  836  837  838  839  840  841 
   3    3    3    3    3    3    3    3    3    3    3    5    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3 
 842  843  844  845  846  847  848  849  850  851  852  853  854  855  856  857  858  859  860  861  862  863  864  865  866  867  868  869  870 
   3    3    3    3    3    3    3    5    3    3    3    3    3    3    3    3    3    3    3    5    3    3    3    3    3    3    3    3    3 
 871  872  873  874  875  876  877  878  879  880  881  882  883  884  885  886  887  888  889  890  891  892  893  894  895  896  897  898  899 
   3    3    3    3    5    3    3    3    3    3    3    3    3    3    3    3    3    2    3    3    3    2    3    3    3    3    3    3    3 
 900  901  902  903  904  905  906  907  908  909  910  911  912  913  914  915  916  917  918  919  920  921  922  923  924  925  926  927  928 
   3    3    3    3    3    3    3    3    3    3    3    5    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3    3 
 929  930  931  932  933  934  935  936  937  938  939  940  941  942  943  944  945  946  947  948  949  950  951  952  953  954  955  956  957 
   3    5    3    3    3    3    3    3    3    5    3    3    3    3    5    3    3    3    3    3    3    3    3    3    3    3    3    3    3 
 958  959  960  961  962  963  964  965  966  967  968  969  970  971  972  973  974  975  976  977  978  979  980  981  982  983  984  985  986 
   3    3    3    3    5    3    3    3    3    3    3    3    3    3    2    3    3    3    3    3    3    3    3    3    5    3    3    3    3 
 987  988  989  990  991  992  993  994  995  996  997  998  999 1000 
   3    3    3    3    3    3    3    3    3    3    3    3    3    3 
 [ reached getOption("max.print") -- omitted 12714 entries ]

Within cluster sum of squares by cluster:
[1] 7.973378e+11 2.754288e+12 2.390668e+12 2.840440e+12 2.436637e+12
 (between_SS / total_SS =  93.6 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss" "betweenss"    "size"         "iter"         "ifault"      
fviz_cluster(final_kmeans2, data = goodreads_kmeans2)

Surprising enough, we receive the exactly same result as we do with only two variables, with every observation being in the same group as the first model.

Web scrapping to insert year data for books/series

As we have has ISBNs associated with books, we can retrieve the year when the book was written by web-scrapping from the website as follow.

goodreads_year <- goodreads
goodreads_year$Year <- 0
for (i in c(1:100)) {
  url_i <- paste('https://isbndb.com/book/',goodreads[i,'isbn'],sep = "")
  webpage_i <- read_html(url_i)
  year_data_html <- html_nodes(webpage_i,'td')
  year_data <- html_text(year_data_html)
  goodreads_year[i,'Year'] <- as.numeric(year_data[6])
}
goodreads_year <- goodreads[goodreads$Year >= 1900,]
goodreads_year <- na.omit(goodreads_year)
goodreads_year

However, this code in R may take a little while to code. As a result, we will proceed with a script in Python, takean from the “Goodreads: Analysis and Recommending Books” notebook on Kaggle.

We can easily see some NAs appearing in the column “Year”. This is due to the fact that the website we are scrapping data from lacks the information “year”. In order to further analyze this dataset with the variable “Year”, we omit all of NAs.

However, instead of doing web-scrapping with R, we decided to use a much more effective tool, Python. The Python code for webscrapping can be found within the same file of this notebook. After running the Python script, we save the csv file as goodreads_year.csv, with encoding “UTF-16”. We import the file as follow.

goodreads_year <- read.csv('/Users/ngohoanganh/Desktop/Goodreads Kaggle project/goodreads_year.csv', stringsAsFactor = FALSE, fileEncoding = "UTF-16")
str(goodreads_year)
'data.frame':   13013 obs. of  11 variables:
 $ bookID            : int  1 2 3 4 5 8 9 10 12 13 ...
 $ title             : chr  "Harry Potter and the Half-Blood Prince (Harry Potter  #6)" "Harry Potter and the Order of the Phoenix (Harry Potter  #5)" "Harry Potter and the Sorcerer's Stone (Harry Potter  #1)" "Harry Potter and the Chamber of Secrets (Harry Potter  #2)" ...
 $ authors           : chr  "J.K. Rowling-Mary GrandPré" "J.K. Rowling-Mary GrandPré" "J.K. Rowling-Mary GrandPré" "J.K. Rowling" ...
 $ average_rating    : num  4.56 4.49 4.47 4.41 4.55 4.78 3.69 4.73 4.38 4.38 ...
 $ isbn              : chr  "0439785960" "0439358078" "0439554934" "0439554896" ...
 $ isbn13            : num  9.78e+12 9.78e+12 9.78e+12 9.78e+12 9.78e+12 ...
 $ language_code     : chr  "eng" "eng" "eng" "eng" ...
 $ X..num_pages      : int  652 870 320 352 435 2690 152 3342 815 815 ...
 $ ratings_count     : int  1944099 1996446 5629932 6267 2149872 38872 18 27410 3602 240189 ...
 $ text_reviews_count: int  26249 27613 70390 272 33964 154 1 820 258 3954 ...
 $ Year              : int  2006 2004 1997 2003 2004 2004 2005 2005 2005 2002 ...
print(paste('There are',nrow(goodreads_year),'books whose `Year` attribute can be retrieved, which accounts for',round(nrow(goodreads_year)/nrow(goodreads)*100, digits = 2),'eprcent of the original dataset.'))
[1] "There are 13013 books whose `Year` attribute can be retrieved, which accounts for 94.89 eprcent of the original dataset."

After retrieving the Year variable, we will proceed to analyse the new dataset with the following graphs:

Average of average_rating of all books for each year

mean_year <- aggregate(average_rating ~ Year, data = goodreads_year, FUN = mean)
mean_year
ggplot(data = mean_year, aes(x = Year, y = average_rating)) +
  geom_line(color = "#FC4E07", size = 1) +
  labs(x = "Year", y = "Average rating", title = "Average rating for each year") + 
  geom_smooth(method = "lm")

goodreads_year[goodreads_year$Year == 1922,]
goodreads_year[goodreads_year$Year == 1931,]

From the graph, we can see two opposite peaks appearing:

  • Maximum peak: The average ratings of books written in 1922 reached the peak of 5.0. After re-checking with the data retrieved, there is only one book written in 1922. Moreover, this book received 0 rating and 0 text review.
  • Minimum peak: The average ratings of books reached its lowest in 1931 with the average of 2.75, then gradually recover until 1940. As expected, in 1931, there is only one book written, with 4 ratings and 0 text review.

Apart from the peaks mentioned above, there is no other abnormal points within the graph. For the remaining years, the average rating fluctuate around 4.0.

Total books of each year

total_books_year <- goodreads_year %>% count(Year)
names(total_books_year) <- c('Year','number_of_books')
total_books_year
ggplot(data = total_books_year, aes(x = Year, y = number_of_books)) + 
  geom_line(color = "#00AFBB", size = 1) +
  labs(x = "Year", y = "Total number of books", title = "Number of books written for each year") + 
  geom_smooth(method = "lm")

We can easily see that, before 1975, there are not many books written in each year. However, after that timestamp, the books published every year start to increase gradually, and skyrocketted between 1990 and 2006. After reaching its peak, the number of books suddenly dropped significantly, and since 2016, there are less than 10 new books written each year.

Total ratings count and average ratings count for each book of each year

sum_ratings_count_year <- aggregate(ratings_count ~ Year, data = goodreads_year, FUN = sum)
sum_ratings_count_year
ggplot(data = sum_ratings_count_year, aes(x = Year, y = ratings_count)) +
  geom_line(color = "purple", size = 1) +
  labs(x = "Year", y = "Total ratings count", title = "Total ratings count of all books for each year") + 
  geom_smooth(method = "lm")

average_ratings_count_year <- aggregate(ratings_count ~ Year, data = goodreads_year, FUN = mean)
average_ratings_count_year
ggplot(data = average_ratings_count_year, aes(x = Year, y = ratings_count)) +
  geom_line(color = "purple", size = 1) +
  labs(x = "Year", y = "Average ratings count", title = "Average ratings count of each book for each year") + 
  geom_smooth(method = "lm")

The total number of ratings each year seems to follow the pattern of the number of books published each year. However, the average ratings count for each book each year does not. We can see two greatest peaks of around 60,000 ratings in 1952 and 2019, along with various other smaller peaks. The average does not seem to follow any pattern.

Total text reviews count and average text reviews count for each book of each year

sum_text_reviews_count_year <- aggregate(text_reviews_count ~ Year, data = goodreads_year, FUN = sum)
sum_text_reviews_count_year
ggplot(data = sum_text_reviews_count_year, aes(x = Year, y = text_reviews_count)) +
  geom_line(color = "#9ACD32", size = 1) +
  labs(x = "Year", y = "Total ratings count", title = "Total text reviews count of all books for each year") + 
  geom_smooth(method = "lm")

average_text_reviews_count_year <- aggregate(text_reviews_count ~ Year, data = goodreads_year, FUN = mean)
average_text_reviews_count_year
ggplot(data = average_text_reviews_count_year, aes(x = Year, y = text_reviews_count)) +
  geom_line(color = "#9ACD32", size = 1) +
  labs(x = "Year", y = "Average text reviews count", title = "Average text reviews count of each book for each year") +
  geom_smooth(method = "lm")

Once again, the total number of text reviews count each year seems to follow the pattern of the number of books written each year. The average reviews count for each book each year reaches it peak in 2019, also along with some other smaller peaks. This time, we can observe that there is an overall increasing trend with respect to time, which means that as time goes, people are leaving more and more text reviews.

LS0tCnRpdGxlOiAiQW5hbHlzaXMgb2YgR29vZHJlYWRzIGRhdGEgb24gS2FnZ2xlIgphdXRob3I6CiAgLSBIb2FuZyBBbmggTkdPOgogICAgICBlbWFpbDogaG9hbmctYW5oLm5nb0Bwb2x5dGVjaG5pcXVlLmVkdQogICAgICBpbnN0aXR1dGU6IMOJY29sZSBQb2x5dGVjaG5pcXVlLCBJUCBQYXJpcwogICAgICBjb3JyZXNwb25kZW5jZTogdHJ1ZQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojT3ZlcnZpZXcKCkZpcnN0IG9mIGFsbCwgd2Ugd2lsbCBtZW50aW9uIGEgYml0IGFib3V0IHRoZSBvd25lciBvZiB0aGlzIGRhdGFzZXQsIEdvb2RyZWFkcyAuIEdvb2RyZWFkcyBpcyBhIHNvY2lhbCBjYXRhbG9naW5nIHdlYnNpdGUgdGhhdCBhbGxvd3MgaW5kaXZpZHVhbHMgdG8gZnJlZWx5IHNlYXJjaCBpdHMgZGF0YWJhc2Ugb2YgYm9va3MsIGFubm90YXRpb25zLCBhbmQgcmV2aWV3cy4KClRoaXMgcHJvamVjdCBhaW1zIHRvIGhhdmUgYW4gaW5zaWdodCBhYm91dCBkaWZmZXJlbnQgbnVtZXJpY2FsIHBhcmFtZXRlcnMgb2YgdGhlIGJvb2sgYW5kIHRoZWlyIHJlbGF0aW9uc2hpcC4gTW9yZW9ldmVyLCBhbG9uZ3NpZGUgd2l0aCB0aGUgaW5mb3JtYWlvbiBnaXZlbiwgd2UgdHJ5IHRvIGRvIGEgYml0IG9mIHdlYi1zY3JhcHBpbmcgYW5kIGdldCBhbm90aGVyIGF0dHJpYnV0ZSAoaW4gdGhpcyBjYXNlLCAnWWVhcicpIGluIG9yZGVyIHRvIHRha2Ugb3VyIGFuYWx5c2lzIGZ1cnRoZXIuIE1vcmVvdmVyLCB0aGlzIHdvdWxkIGJlIG15IGZpcnN0IG1pbGVzdG9uZSBpbnRvIERhdGEgQW5hbHlzaXMsIHVzaW5nIGFsbCB0aGUgc2tpbGxzIGFuZCBrbm93bGVkZ2VzIEkgaGF2ZSBhY3F1aXJlZCBkdXJpbmcgbXkgc3VtbWVyIHRpbWUgd29ya2luZyB3aXRoIFIuCgpUaGlzIHByb2plY3QgaXMgbWFkZSBwb3NzaWJsZSBieSB1c2VyICoqU291bWlrKiogb24gS2FnZ2xlLCBhbG9uZyB3aXRoIHRoZSB3b25kZXJmdWwgcmVmZXJlbmNlIGZyb20gdGhlIFB5dGhvbiBub3RlYm9vayBuYW1lZCAqKkdvb2RyZWFkczogQW5hbHlzaXMgYW5kIFJlY29tbWVuZGluZyBCb29rcyoqIG9mICpTaGl2YW0gUmFsbGkqLiBJIHdvdWxkIGxpa2UgdG8gc2VuZCBteSBzaW5jZXJlIHRoYW5rcyB0byBib3RoIG9mIHRoZW0uCgpBbGwgb2YgdGhlIGRhdGFzZXRzIGFuZCBzY3JpcHRzIHVzZWQgd2lsbCBiZSB1cGxvYWRlZCBhbG9uZyB3aXRoIHRoaXMgbm90ZWJvb2suIAoKI0xvYWRpbmcgbGlicmFyaWVzIGFuZCB1bmRlcnN0YW5kaW5nIHRoZSBiYXNpY3Mgb2YgdGhpcyBkYXRhc2V0CgojI0xvYWRpbmcgbGlicmFyaWVzCgpGaXJzdCwgd2Ugd2lsbCBsb2FkIGFsbCB0aGUgbmVjZXNzYXJ5IGxpYnJhcmllcyBmb3IgdGhpcyBub3RlYm9vay4KCmBgYHtyfQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpsaWJyYXJ5KHZpcmlkaXMpICAKbGlicmFyeShXVlBsb3RzKQpsaWJyYXJ5KG5hbmlhcikKbGlicmFyeShlMTA3MSkKbGlicmFyeShwbG90cml4KQpsaWJyYXJ5KGdnY29ycnBsb3QpCmxpYnJhcnkod2FmZmxlKQpsaWJyYXJ5KGV4dHJhZm9udCkgCmxpYnJhcnkoR0dhbGx5KQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShjbHVzdGVyKQpsaWJyYXJ5KGZhY3RvZXh0cmEpCmxpYnJhcnkocnZlc3QpCmBgYAoKIyNVbmRlcnN0YW5kaW5nIHRoZSBiYXNpYyBzdHJ1Y3R1cmUgb2YgdGhlIGRhdGFzZXQKCldlIGltcG9ydCBhbmQgaW52ZXN0aWdhdGUgdGhlIHN0cnVjdHVyZSBvZiB0aGUgZGF0YXNldC4KCmBgYHtyfQpnb29kcmVhZHMgPC0gcmVhZC5jc3YoJy9Vc2Vycy9uZ29ob2FuZ2FuaC9EZXNrdG9wL0dvb2RyZWFkcyBLYWdnbGUgcHJvamVjdC9ib29rcy5jc3YnLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCnN0cihnb29kcmVhZHMpCmhlYWQoZ29vZHJlYWRzKQpwcmludChwYXN0ZSgnVGhlIG9yaWdpbmFsIGdvb2RyZWFkcyBib29rIGRhdGFzZXQgaGFzJyxucm93KGdvb2RyZWFkcyksJ3Jvd3MgYW5kJyxuY29sKGdvb2RyZWFkcyksJ2NvbHVtbnMuJykpCmBgYAoKIyNGaXhpbmcgcHJvYmxlbXMgb2YgdGhlIGRhdGFzZXQKCkZpcnN0IG9mIGFsbCwgd2Uga25vdyB0aGF0IHRoaXMgZGF0YXNldCBoYXMgMTM3MjQgb2JzZXJ2YXRpb25zIHdpdGggMTAgdmFyaWFibGVzLiBUaGVyZSBpcyBvbmUgdmFyaWFibGUgdGhhdCBpcyBzdXBwb3NlZCB0byBiZSB3cml0dGVuIGFzICIjIG51bV9wYWdlcyIuIEhvd2V2ZXIsIGFzIFIgY2Fubm90IHJlYWQgc3BlY2lhbCBjaGFyYWN0ZXJzIGluIGNvbHVtbiBuYW1lcywgZm9yIHRoZSBzYWtlIG9mIGNvbnZlbmllbmNlLCB3ZSByZW5hbWUgdGhpcyB2YXJpYWJsZSBhcyAibnVtX3BhZ2VzIi4gCgpXZSBvYnZlcnNlIHNvbWUgcHJvYmxlbXMgd2l0aCB0aGlzIGRhdGFzZXQuIEF0IGNvbHVtbnMgNCBhbmQgOCAoY29ycmVzcG9uZGluZyB0byB2YXJpYWJsZXMgImF2ZXJhZ2VfcmF0aW5nIiBhbmQgIm51bV9wYWdlIiksIHdlIGNhbiBzZWUgdGhhdCB0aGUgb2JzZXJ2YXRpb25zIGFyZSBvZiB0eXBlICJjaGFyYWN0ZXIiLCB3aGlsZSB0aGV5IHNob3VsZCBiZSBvZiB0eXBlICJpbnRlZ2VyIi4gV2UgZml4IHRoZSBwcm9ibGVtIHdpdGggdGhlIGZ1bmN0aW9uIGFzLm51bWVyaWMgaW4gdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rLgoKSG93ZXZlciwgd2hpbGUgZml4aW5nIHRoZSBwcm9ibGVtIGFib3ZlLCB0aG9zZSB0d28gY29sdW1ucyB5aWVsZCBzb21lIE5BIHZhbHVlcy4gVGhpcyBjYW4gYmUgZHVlIHRvIHRob3NlIHJvd3MgaGF2aW5nIHRvbyBtYW55IGNvbW1hcywgZm9yY2luZyB0aGUgdmFsdWVzIHRoYXQgc2hvdWxkIGJlbG9uZyB0byBvbmUgY29sdW1ucyBtb3ZlIHRvIGFub3RoZXIgY29sdW1uLiBXZSBvbWl0IHRoZXNlIG91dGxpZXJzIGZvciBhIGNsZWFuIGRhdGFzZXQgYnkgdXNpbmcgdGhlIGZ1bmN0aW9uIG5hLm9taXQuIAoKTW9yZW92ZXIsIG9uZSBvZiB0aGUgZmFtb3VzIGF1dGhvcnMgdGhhdCB3ZSBrbm93LCBKLksgUm93bGluZywgaGFzIGEgZmFpcmx5IGxvbmcgbmFtZSBvZiAiSi5LIFJvd2xpbmctTWFyeSBHcmFuZFByw6kiLiBBZnRlciBjaGFuZ2luZyB0aGUgYXV0aG9yJ3MgbmFtZSwgd2UgaGF2ZSB0aGUgZGF0YXNldCBhdmFpbGFibGUgZm9yIGZ1cnRoZXIgYW5hbHlzaXMuCgpgYGB7cn0KZ29vZHJlYWRzW2dvb2RyZWFkcz09J0ouSy4gUm93bGluZy1NYXJ5IEdyYW5kUHLDqSddIDwtICdKLksuIFJvd2xpbmcnCmhlYWQoZ29vZHJlYWRzKQpmb3IoaSBpbiBjKDQsOCkpIHsKICBnb29kcmVhZHNbLGldIDwtIGFzLm51bWVyaWMoZ29vZHJlYWRzWyxpXSkKfQpuYW1lcyhnb29kcmVhZHMpW25hbWVzKGdvb2RyZWFkcykgPT0gIlguLm51bV9wYWdlcyJdIDwtICJudW1fcGFnZXMiCmdvb2RyZWFkcyA8LSBuYS5vbWl0KGdvb2RyZWFkcykKc3RyKGdvb2RyZWFkcykgIApwcmludChwYXN0ZSgnVGhlIGZpbmFsIGdvb2RyZWFkcyBib29rIGRhdGFzZXQgaGFzJyxucm93KGdvb2RyZWFkcyksJ3Jvd3MgYW5kJyxuY29sKGdvb2RyZWFkcyksJ2NvbHVtbnMuJykpCmBgYAoKQWZ0ZXIgZml4aW5nIGFsbCB0aGUgZXJyb3JzIGFwcGVhcmVkIGluIHRoZSBkYXRhc2V0LCB3ZSBzdGFydCB0byBkZWZpbmUgdGhlIHZhcmlhYmxlcycgbmFtZXMuIFRoZWlyIGRlc2NyaXB0aW9uIGlzIGFzIGZvbGxvdzoKCiAgKiAqKmJvb2tJRCoqOiB0aGUgdW5pcXVlIElEIGZvciBlYWNoIGJvb2svc2VyaWVzCiAgKiAqKnRpdGxlKio6IHRoZSB0aXRsZSBvZiBlYWNoIGJvb2svc2VyaWVzCiAgKiAqKmF1dGhvcnMqKjogdGhlIGF1dGhvcihzKSBvZiB0aGUgcGFydGljdWxhciBib29rL3NlcmllcwogICogKiphdmVyYWdlX3JhdGluZyoqOiB0aGUgYXZlcmFnZSByYXRpbmdzIG9mIHRoZSBwYXJ0aWN1bGFyIGJvb2svc2VyaWVzLCBnaXZlbiBieSBHb29kcmVhZHMnIHVzZXJzCiAgKiAqKklTQk4qKjogb2xkIElTQk4gbnVtYmVyIG9mIDEwIGRpZ2l0cywgaW5jbHVkaW5nIGluZm9ybWF0aW9uIGFib3V0IGEgYm9vay9zZXJpZXMsIGZvciBleGFtcGxlIG5hbWUsIGF1dGhvcihzKSwgcHVibGlzaGVyLCBnZW5yZSwgZXRjLgogICogKipJU0JOMTMqKjogbmV3IElTQk4gbnVtYmVyIG9mIDEzIGRpZ2l0cywgaW50cm9kdWNlZCBpbiAyMDA3CiAgKiAqKkxhbmd1YWdlX2NvZGUqKjogMy1jaGFyYWN0ZXIgbGFuZ3VhZ2UgY29kZSwgd2hpY2ggaW5kaWNhdGVzIHRoZSBsYW5ndWFnZSBpbiB3aGljaCB0aGUgYm9vayBpcyB3cml0dGVuCiAgKiAqKk51bV9wYWdlKio6IG51bWJlciBvZiBwYWdlcyBvZiBlYWNoIGJvb2svc2VyaWVzCiAgKiAqKlJhdGluZ3NfY291bnQqKjogbnVtYmVyIG9mIHJhdGluZ3MgZ2l2ZW4gYnkgdXNlcnMgZm9yIGVhY2ggYm9vay9zZXJpZXMKICAqICoqVGV4dF9yZXZpZXdzX2NvdW50Kio6IG51bWJlciBvZiB0ZXh0IHJldmlld3MgKGFwYXJ0IGZyb20gcG9pbnQgcmV2aWV3cyBvZiBzY2FsZSA1KSBnaXZlbiBieSB1c2VycyBmb3IgZWFjaCBib29rL3NlcmllcwogIAojI01pc3NpbmduZXNzIG9mIGRhdGEKV2UgaW52ZXN0aWdhdGUgd2hldGhlciB0aGVyZSBpcyBhbnkgZGF0YSBjZWxsIG1pc3Npbmcgd2l0aCB0aGUgdmlzX21pc3MgcGxvdC4gU2luY2Ugd2UgZG8gbm90IHBsb3QgYW55IHNpZ25pZmljYW50IGluIHRoZSBwbG90LCBhbmQgaXQgc2hvd3MgdGhhdCBsZXNzIHRoYW4gMC4xJSBvZiB0aGUgZGF0YSBpcyBtaXNzaW5nLCB3ZSB1c2UgZ2dfbWlzc192YXIgdG8gZ2V0IHRoZSBleGFjdCBudW1iZXIgb2YgZGF0YSBjZWxscyBtaXNzaW5nIGluIGVhY2ggY29sdW1uLiBUaGUgcmVzdWx0IGlzLCBzdXJwcmlzaW5nbHksIHRoZXJlIGlzIG5vIGRhdGEgbWlzaXNpbmcuCiAgCmBgYHtyfQp2aXNfbWlzcyhnb29kcmVhZHMpCmdnX21pc3NfdmFyKGdvb2RyZWFkcykKYGBgCiAgCiNFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzIChFREEpIG9mIHRoZSBkYXRhc2V0IEdvb2RyZWFkcwoKSW4gdGhpcyBwYXJ0LCB3ZSB3aWxsIGFuc3dlciBzb21lIG9mIHRoZSBpbnRlcmVzdGluZyBleHBsb3JhdG9yeSBxdWVzdGlvbnMgYnkgdmlzdWFsaXppbmcgZGF0YS4KCiMjQ29ycmVsYXRpb24gYmV0d2VlbiBudW1lcmljYWwgY29sdW1ucyAKCkZpcnN0LCB3ZSB3aWxsIHNlZSB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiBudW1lcmljYWwgY29sdW1ucyBvZiB0aGlzIGRhdGFzZXQgYnkgdXNpbmcgUGVhcnNvbidzIFIsIEtlbmRhbGwgcmFuayBhbmQgU3BlYXJtYW4gY29ycmVsYXRpb24gY29lZmZpY2llbnQuCgogICogUGVhcnNvbidzIFIgY29ycmVsYXRpb24gY29lZmZpY2llbnQgaXMgdXNlZCB0byBleGFtaW5lIHRoZSBzdHJlbmd0aCBhbmQgZGlyZWN0aW9uIG9mIHRoZSBsaW5lYXIgcmVsYXRpb25zaGlwIGJldHdlZW4gdHdvIGNvbnRpbnVvdXMgdmFyaWFibGVzLgogICogS2VuZGFsbCByYW5rIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50IChvciBLZW5kYWxsIHRhdSdzIGNvZWZmaWNpZW50KSBpcyBhIHN0YXRpc3RpYyB1c2VkIHRvIG1lYXN1cmUgdGhlIG9yZGluYWwgYXNzb2NpYXRpb24gYmV0d2VlbiB0d28gbWVhc3VyZWQgcXVhbnRpdGllcy4gCiAgKiBTcGVhcm1hbidzIHJhbmsgY29ycmVsYXRpb24gY29lZmZpY2llbnQgKG9yIFNwZWFybWFuJ3MgcmhvKSBpcyBhIG5vbi1wYXJhbWV0cmljIG1lYXN1cmUgb2YgcmFuayBjb3JyZWxhdGlvbiAoc3RhdGlzdGljYWwgZGVwZW5kZW5jZSBiZXR3ZWVuIHRoZSByYW5raW5ncyBvZiB0d28gdmFyaWFibGVzKS4gSXQgYXNzZXNzZXMgaG93IHdlbGwgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHR3byB2YXJpYWJsZXMgY2FuIGJlIGRlc2NyaWJlZCB1c2luZyBhIG1vbm90b25pYyBmdW5jdGlvbi4KCmBgYHtyfQpnb29kcmVhZHNfY29yciA8LSBzdWJzZXQoZ29vZHJlYWRzLCBzZWxlY3QgPSBjKCdhdmVyYWdlX3JhdGluZycsJ251bV9wYWdlcycsJ3JhdGluZ3NfY291bnQnLCd0ZXh0X3Jldmlld3NfY291bnQnKSkKCmdvb2RyZWFkc19jb3JyX3BlYXJzb25fcm91bmQyIDwtIHJvdW5kKGNvcihnb29kcmVhZHNfY29yciwgbWV0aG9kID0gInBlYXJzb24iLCB1c2UgPSAicGFpcndpc2UuY29tcGxldGUub2JzIiksIDIpCmRhdGEuZnJhbWUoZ29vZHJlYWRzX2NvcnJfcGVhcnNvbl9yb3VuZDIpCgpnb29kcmVhZHNfY29ycl9rZW5kYWxsX3JvdW5kMiA8LSByb3VuZChjb3IoZ29vZHJlYWRzX2NvcnIsIG1ldGhvZCA9ICJrZW5kYWxsIiwgdXNlID0gInBhaXJ3aXNlLmNvbXBsZXRlLm9icyIpLCAyKQpkYXRhLmZyYW1lKGdvb2RyZWFkc19jb3JyX2tlbmRhbGxfcm91bmQyKQoKZ29vZHJlYWRzX2NvcnJfc3BlYXJtYW5fcm91bmQyIDwtIHJvdW5kKGNvcihnb29kcmVhZHNfY29yciwgbWV0aG9kID0gInNwZWFybWFuIiwgdXNlID0gInBhaXJ3aXNlLmNvbXBsZXRlLm9icyIpLCAyKQpkYXRhLmZyYW1lKGdvb2RyZWFkc19jb3JyX3NwZWFybWFuX3JvdW5kMikKCmdnY29ycnBsb3QoZ29vZHJlYWRzX2NvcnJfcGVhcnNvbl9yb3VuZDIsIGhjLm9yZGVyID0gVFJVRSwKICAgbGFiID0gVFJVRSwKICAgdGl0bGUgPSAiUGVhcnNvbidzIFIgY29ycmVsYXRpb24gbWF0cml4IGZvciBHb29kcmVhZHMgbnVtZXJpY2FsIHZhcmlhYmxlcyIsCiAgIG91dGxpbmUuY29sID0gIndoaXRlIiwKICAgZ2d0aGVtZSA9IGdncGxvdDI6OnRoZW1lX2dyYXksCiAgIGNvbG9ycyA9IGMoIiM2RDlFQzEiLCAid2hpdGUiLCAiI0U0NjcyNiIpKQoKCmdnY29ycnBsb3QoZ29vZHJlYWRzX2NvcnJfa2VuZGFsbF9yb3VuZDIsIGhjLm9yZGVyID0gVFJVRSwKICAgbGFiID0gVFJVRSwKICAgdGl0bGUgPSAiS2VuZGFsbCBjb3JyZWxhdGlvbiBtYXRyaXggZm9yIEdvb2RyZWFkcyBudW1lcmljYWwgdmFyaWFibGVzIiwKICAgb3V0bGluZS5jb2wgPSAid2hpdGUiLAogICBnZ3RoZW1lID0gZ2dwbG90Mjo6dGhlbWVfZ3JheSwKICAgY29sb3JzID0gYygiIzZEOUVDMSIsICJ3aGl0ZSIsICIjRTQ2NzI2IikpCgpnZ2NvcnJwbG90KGdvb2RyZWFkc19jb3JyX3NwZWFybWFuX3JvdW5kMiwgaGMub3JkZXIgPSBUUlVFLAogICBsYWIgPSBUUlVFLAogICB0aXRsZSA9ICJTcGVhcm1hbiBjb3JyZWxhdGlvbiBtYXRyaXggZm9yIEdvb2RyZWFkcyBudW1lcmljYWwgdmFyaWFibGVzIiwKICAgb3V0bGluZS5jb2wgPSAid2hpdGUiLAogICBnZ3RoZW1lID0gZ2dwbG90Mjo6dGhlbWVfZ3JheSwKICAgY29sb3JzID0gYygiIzZEOUVDMSIsICJ3aGl0ZSIsICIjRTQ2NzI2IikpCmBgYAoKRnJvbSB0aGUgdGhyZWUgY29ycmVsYXRpb24gbWF0cmljZXMgYWJvdmUsIHdlIGNhbiBzZWUgdGhhdCB0aGVyZSBpcyBvbmx5IG9uZSBwYWlyIG9mIG51bWVyaWNhbCB2YXJpYWJsZXMgdGhhdCBhcmUgc3Ryb25nbHkgY29ycmVsYXRlZCB0byBlYWNoIG90aGVyLCB3aGljaCBpcyByYXRpbmdzX2NvdW50IGFuZCB0ZXh0X3Jldmlld3NfY291bnQuIEFsbCBvZiB0aGUgY29ycmVsYXRpb24gY29lZmZpY2llbnRzIG9mIHRoZXNlIHR3byB2YXJpYWJsZXMsIHJlZ2FyZGxlc3Mgb2YgdGhlIG1ldGhvZCB1c2VkLCBhcmUgYWx3YXlzIGdyZWF0ZXIgdGhhbiAwLjgsIHdoaWNoIGNhbiBiZSBjb25zaWRlcmVkIHRvIGJlIGEgc3Ryb25nIGNvcnJlbGF0aW9uLiBUaGUgcmVtYWluaW5nIHBhaXJzIG9mIHZhcmlhYmxlcyBzaG93IGxpdHRsZSBwb3NpdGl2ZSBjb3JyZWxhdGlvbiwgd2l0aCBhbGwgdGhlIHJlbWFpbmluZyBjb2VmZmljaWVudHMgb2YgbGVzcyB0aGFuIDAuMiwgc29tZSBhcmUgZXZlbiBsZXNzIHRoYW4gMC4wNS4KClRoaXMgc2hvd3MgYW4gaW50dWl0aXZlIHJlc3VsdCB0aGF0IHRoZSBtb3JlIHJlYWRlcnMgcmF0ZSBhIGJvb2sgb24gYSBzY2FsZSBvZiAwIHRvIDUsIHRoZSBtb3JlIHJlYWRlcnMgd2lsbCBsZWF2ZSBhIHdyaXR0ZW4gdGV4dCByZXZpZXcgZm9yIHRoZSBzYW1lIGJvb2suCgojI0hpc3RvZ3JhbSBwbG90IG9mIG51bWVyaWNhbCB2YXJpYWJsZXMKCkZpcnN0LCB3ZSB3aWxsIHBsb3QgdGhlIGhpc3RvZ3JhbXMgb2YgdGhlIG51bWVyaWNhbC9pbnRlZ2VyIHZhcmlhYmxlcywgaW5jbHVkaW5nIGF2ZXJhZ2VfcmF0aW5nLCBudW1fcGFnZXMsIHJhdGluZ3NfY291bnQgYW5kIHRleHRfcmV2aWV3c19jb3VudC4gCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBnb29kcmVhZHMsIGFlcyh4ID0gZ29vZHJlYWRzJGF2ZXJhZ2VfcmF0aW5nKSkgKyAKICAgIGdlb21faGlzdG9ncmFtKGFlcyh5ID0gLi5kZW5zaXR5Li4pLGJpbndpZHRoID0gMC4wMSwgY29sb3I9J29yYW5nZScsIGZpbGw9J29yYW5nZScpICsKICAgIGdlb21fZGVuc2l0eShjb2xvdXI9ImJsdWUiLCBsd2QgPSAwLjUsIGFscGhhPTAuNSkgKyAKICAgIGxhYnModGl0bGU9Ikhpc3RvZ3JhbSBmb3IgYm9va3MnIGF2ZXJhZ2UgcmF0aW5ncyIsIHggPSAiQXZlcmFnZSByYXRpbmdzIiwgeSA9ICJOdW1iZXIgb2YgYm9va3MiKSArIAogICAgZ2VvbV92bGluZShkYXRhID0gZ29vZHJlYWRzLCB4aW50ZXJjZXB0ID0gbWVhbihnb29kcmVhZHMkYXZlcmFnZV9yYXRpbmcsIG5hLnJtID0gVFJVRSksIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIsIHNpemUgPSAwLjUpCgpnZ3Bsb3QoZGF0YSA9IGdvb2RyZWFkcywgYWVzKHggPSBnb29kcmVhZHMkbnVtX3BhZ2VzKSkgKyAKICAgIGdlb21faGlzdG9ncmFtKGFlcyh5ID0gLi5kZW5zaXR5Li4pLGJpbndpZHRoID0gMTAsIGNvbG9yPSdvcmFuZ2UnLCBmaWxsPSdvcmFuZ2UnKSArCiAgICBnZW9tX2RlbnNpdHkoY29sb3VyPSJibHVlIiwgbHdkID0gMC41LCBhbHBoYT0wLjUpICsgCiAgICBsYWJzKHRpdGxlPSJIaXN0b2dyYW0gZm9yIGJvb2tzJyBudW1iZXIgb2YgcGFnZXMiLCB4ID0gIk51bWJlciBvZiBwYWdlcyIsIHkgPSAiTnVtYmVyIG9mIGJvb2tzIikgKyAKICAgIGdlb21fdmxpbmUoZGF0YSA9IGdvb2RyZWFkcywgeGludGVyY2VwdCA9IG1lYW4oZ29vZHJlYWRzJGF2ZXJhZ2VfcmF0aW5nLCBuYS5ybSA9IFRSVUUpLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMC41KQoKZ2dwbG90KGRhdGEgPSBnb29kcmVhZHMsIGFlcyh4ID0gZ29vZHJlYWRzJHJhdGluZ3NfY291bnQpKSArIAogICAgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLiksYmlud2lkdGggPSAxMDAsIGNvbG9yPSdvcmFuZ2UnLCBmaWxsPSdvcmFuZ2UnKSArCiAgICBnZW9tX2RlbnNpdHkoY29sb3VyPSJibHVlIiwgbHdkID0gMC41LCBhbHBoYT0wLjUpICsgCiAgICBsYWJzKHRpdGxlPSJIaXN0b2dyYW0gZm9yIHJhdGluZ3MgY291bnQgb2YgZWFjaCBib29rL3NlcmllcyIsIHggPSAiUmF0aW5ncyBjb3VudCIsIHkgPSAiTnVtYmVyIG9mIGJvb2tzIikgKyAKICAgIGdlb21fdmxpbmUoZGF0YSA9IGdvb2RyZWFkcywgeGludGVyY2VwdCA9IG1lYW4oZ29vZHJlYWRzJGF2ZXJhZ2VfcmF0aW5nLCBuYS5ybSA9IFRSVUUpLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMC41KQoKZ2dwbG90KGRhdGEgPSBnb29kcmVhZHMsIGFlcyh4ID0gZ29vZHJlYWRzJHRleHRfcmV2aWV3c19jb3VudCkpICsgCiAgICBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9IC4uZGVuc2l0eS4uKSxiaW53aWR0aCA9IDUwICwgY29sb3I9J29yYW5nZScsIGZpbGw9J29yYW5nZScpICsKICAgIGdlb21fZGVuc2l0eShjb2xvdXI9ImJsdWUiLCBsd2QgPSAwLjUsIGFscGhhPTAuNSkgKyAKICAgIGxhYnModGl0bGU9Ikhpc3RvZ3JhbSBmb3IgdGV4dCByZXZpZXdzIGNvdW50IGZvciBlYWNoIGJvb2svc2VyaWVzIiwgeCA9ICJUZXh0IHJldmlld3MgY291bnQiLCB5ID0gIk51bWJlciBvZiBib29rcyIpICsgCiAgICBnZW9tX3ZsaW5lKGRhdGEgPSBnb29kcmVhZHMsIHhpbnRlcmNlcHQgPSBtZWFuKGdvb2RyZWFkcyRhdmVyYWdlX3JhdGluZywgbmEucm0gPSBUUlVFKSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDAuNSkKYGBgCgpUaGUgaGlzdG9ncmFtIG9mIGF2ZXJhZ2VfcmF0aW5ncyBzaG93cyB0aGF0IG1vc3Qgb2YgdGhlIHJhdGluZ3MgbGllIGJldHdlZW4gMyBhbmQgNS4gVGhlIGRpc3RyaWJ1dGlvbiBvZiByYXRpbmdzIGJldHdlZW4gMyBhbmQgNSBzZWVtcyB0byBiZSBmYW1pbGlhciB0byBhIG5vcm1hbCBkaXN0cmlidXRpb24gKGkuZSBtb3N0IG9mIHRoZSByYXRpbmdzIGZvY3VzIGFyb3VuZCA0LCBhbmQgdGhlIGRlbnNpdHkgZGVjcmVhc2VzIHVwb24gcmVhY2hpbmcgdGhlIGVkZ2VzKS4gTGF0ZXIsIHdlIHdpbGwgaW52ZXN0aWdhdGUgdGhpcyBkaXN0cmlidXRpb24gYW5kIHRlc3QgaG93IGNsb3NlIGlzIGl0IHRvIHRoZSBub3JtYWwgZGlzdHJpYnV0aW9uLgoKV2l0aCB0aGUgaGlzdG9ncmFtIG9mIG51bV9wYWdlcywgbW9zdCBvZiB0aGUgYm9va3MgaGF2ZSBsZXNzIHRoYW4gMTAwMCBwYWdlcywgd2l0aCBtYW55IG9mIHRoZW0gaGF2aW5nIDI1MC01MDAgcGFnZXMuIExhdGVyLCB3ZSB3aWxsIGFsc28gem9vbSBpbnRvIHRoaXMgcGFydCBvZiB0aGUgaGlzdG9ncmFtIHRvIGhhdmUgYSBjbG9zZXIgbG9vayBvZiB0aGUgZGlzdHJpYnV0aW9uLgoKVGhlIGhpc3RvZ3JhbXMgb2YgcmF0aW5nc19jb3VudCBhbmQgdGV4dF9yZXZpZXdzX2NvdW50IGlzIHNvIG11Y2ggaGVhdmlseSBza2V3ZWQgdG8gdGhlIGxlZnQgdGhhdCB3ZSBjYW4gb25seSBzZWUgYSBzdHJhaWdodCBzbWFsbCBiYXIgYXQgdGhlIHBvc2l0aW9uIDAsIGFuZCB0aGUgZGVuc2l0eSBjdXJ2ZSBkZWNyZWFzZXMgdG8gMCBzaG9ydGx5LiBBcyBzdWNoLCB0byBmdXJ0aGVyIGludmVzdGlnYXRlIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlc2UgdmFyaWFibGVzLCB3ZSBoYXZlIHRvIGxhdGVyIHRha2UgYSBjbG9zZXIgbG9vayBhdCB0aGUgcG9zaXRpb25zIGFyb3VuZCAwLiAKCiMjQmFzaWMgc3RhdGlzdGljcyBvZiBudW1lcmljYWwgdmFyaWFibGVzCgpGaXJzdCwgYXMgUiBkb2VzIG5vdCBoYXZlIGEgYnVpbHQgaW4gZnVuY3Rpb24gdG8gY2FsY3VsYXRlIHRoZSBtb2RlIG9mIGEgZGF0YXNldCwgd2UgcHJvY2VlZCB0byBkZWZpbmUgdGhhdCBmdW5jdGlvbiBvdXJzZWx2ZXMgYXMgZm9sbG93LgoKYGBge3J9CiMgRGVmaW5lIHRoZSBmdW5jdGlvbiBnZXRtb2RlIGluIFIgdG8gZ2V0IHRoZSBtb2RlIG9mIGRhdGEgKGFwcGVhcmFuY2Ugd2l0aCBoaWdoZXN0IGZyZXF1ZW5jeSkKZ2V0bW9kZSA8LSBmdW5jdGlvbih2KSB7CiAgIHVuaXF2IDwtIHVuaXF1ZSh2KQogICB1bmlxdlt3aGljaC5tYXgodGFidWxhdGUobWF0Y2godiwgdW5pcXYpKSldCn0KYGBgCgpOb3csIHdlIHdpbGwgZ2VuZXJhdGUgYSB0YWJsZSBvZiBiYXNpYyBwcm9wZXJ0aWVzIG9mIGVhY2ggdmFyaWFibGUuCgpgYGB7cn0KYmFzaWNfc3RhdGlzdGljc19nb29kcmVhZHMgPC0gZGF0YS5mcmFtZShtYXRyaXgoTkEsIG5jb2wgPSAxMywgbnJvdyA9IDEwKSkKbmFtZXMoYmFzaWNfc3RhdGlzdGljc19nb29kcmVhZHMpIDwtIGMoJ1ZhcmlhYmxlJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnTWluaW11bSBzY29yZScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzI1dGggcGVyY2VudGlsZScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ01lZGlhbicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ01lYW4gc2NvcmUnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICc3NXRoIHBlcmNlbnRpbGUnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICdNYXhpbXVtIHNjb3JlJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnVmFsdWUgd2l0aCBoaWdoZXN0IGZyZXF1ZW5jeSAobW9kZSknLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICdGcmVxdWVuY3kgb2YgbW9kZScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1N0YW5kYXJkIERldmlhdGlvbicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1NFIE1lYW4nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICdTa2V3bmVzcycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0t1cnRvc2lzJykKCmZvcihpIGluIGMoNCw4OjEwKSkgewogIGJhc2ljX3N0YXRpc3RpY3NfZ29vZHJlYWRzW2ksMV0gPC0gY29sbmFtZXMoZ29vZHJlYWRzKVtpXQogIGJhc2ljX3N0YXRpc3RpY3NfZ29vZHJlYWRzW2ksMl0gPC0gbWluKGdvb2RyZWFkc1ssaV0sIG5hLnJtID0gVFJVRSkKICBiYXNpY19zdGF0aXN0aWNzX2dvb2RyZWFkc1tpLDNdIDwtIHF1YW50aWxlKGdvb2RyZWFkc1ssaV0sIDAuMjUsIG5hLnJtID0gVFJVRSkKICBiYXNpY19zdGF0aXN0aWNzX2dvb2RyZWFkc1tpLDRdIDwtIHF1YW50aWxlKGdvb2RyZWFkc1ssaV0sIDAuNSwgbmEucm0gPSBUUlVFKQogIGJhc2ljX3N0YXRpc3RpY3NfZ29vZHJlYWRzW2ksNV0gPC0gbWVhbihnb29kcmVhZHNbLGldLCBuYS5ybSA9IFRSVUUpCiAgYmFzaWNfc3RhdGlzdGljc19nb29kcmVhZHNbaSw2XSA8LSBxdWFudGlsZShnb29kcmVhZHNbLGldLCAwLjc1LCBuYS5ybSA9IFRSVUUpCiAgYmFzaWNfc3RhdGlzdGljc19nb29kcmVhZHNbaSw3XSA8LSBtYXgoZ29vZHJlYWRzWyxpXSwgbmEucm0gPSBUUlVFKQogIGJhc2ljX3N0YXRpc3RpY3NfZ29vZHJlYWRzW2ksOF0gPC0gZ2V0bW9kZShuYS5vbWl0KGdvb2RyZWFkc1ssaV0pKQogIGJhc2ljX3N0YXRpc3RpY3NfZ29vZHJlYWRzW2ksOV0gPC0gc3VtKGdvb2RyZWFkc1ssaV0gPT0gYmFzaWNfc3RhdGlzdGljc19nb29kcmVhZHNbaSw4XSkKICBiYXNpY19zdGF0aXN0aWNzX2dvb2RyZWFkc1tpLDEwXSA8LSBzZChnb29kcmVhZHNbLGldLCBuYS5ybSA9IFRSVUUpCiAgYmFzaWNfc3RhdGlzdGljc19nb29kcmVhZHNbaSwxMV0gPC0gc3RkLmVycm9yKGdvb2RyZWFkc1ssaV0sIG5hLnJtID0gVFJVRSkKICBiYXNpY19zdGF0aXN0aWNzX2dvb2RyZWFkc1tpLDEyXSA8LSBza2V3bmVzcyhnb29kcmVhZHNbLGldLCBuYS5ybSA9IFRSVUUpCiAgYmFzaWNfc3RhdGlzdGljc19nb29kcmVhZHNbaSwxM10gPC0ga3VydG9zaXMoZ29vZHJlYWRzWyxpXSwgbmEucm0gPSBUUlVFKQp9CgpiYXNpY19zdGF0aXN0aWNzX2dvb2RyZWFkcyA8LSBuYS5vbWl0KGJhc2ljX3N0YXRpc3RpY3NfZ29vZHJlYWRzKQpWaWV3KGJhc2ljX3N0YXRpc3RpY3NfZ29vZHJlYWRzKQpgYGAKCkZpcnN0IG9mIGFsbCwgd2Ugc2VlIGFuIGludGVyZXN0aW5nIGZhY3QgdGhhdCB0aGUgbW9kZSBvZiB0ZXh0X3Jldmlld3NfY291bnQgaXMgMCAobW9yZSB0aGFuIDkwMCBib29rcyByZWNlaXZlIDAgdGV4dCByZXZpZXdzKSwgd2hpY2ggbWVhbnMgdGhhdCBib29rcyBoYXZpbmcgMCB0ZXh0IHJldmlld3Mgb2NjdXIgdGhlIG1vc3QgZnJlcXVlbnRseSBpbiB0aGUgZGF0YXNldC4gTW9yZW92ZXIsIHRoZSBtb2RlIG9mIGF2ZXJhZ2VfcmF0aW5ncyBpcyBhbHNvIGV4YWN0bHkgNC4wLCB3aGljaCBzaG93cyB0aGF0IHJlYWRlcnMgYXJlIHJlYWxseSBnZW5lcm91cyB3aXRoIHRoZSBib29rcyB0aGV5IGFyZSByZWFkaW5nLgoKQW1vbmcgdGhlIDQgdmFyaWFibGVzLCB0aGVyZSBpcyBvbmx5IG9uZSB2YXJpYWJsZSAoYXZlcmFnZV9yYXRpbmdzKSB0aGF0IGhhcyBhIG5lZ2F0aXZlIHNrZXduZXNzLiBUaGlzIGlzIGR1ZSB0byB0aGUgZmFjdCB0aGF0IHRoZSBoaXN0b2dyYW0gb2YgdGhpcyB2YXJpYWJsZSBza2V3cyBoZWF2aWx5IHRvIHRoZSByaWdodCwgd2l0aCBtb3N0IG9mIHRoZSBvYnNlcnZhdGlvbnMgbGllIGJldHdlZW4gMyBhbmQgNS4gVGhlIHRocmVlIHJlbWFpbmluZyB2YXJpYWJsZXMgaGFzIGEgc2lnbmlmaWNhbnRseSBwb3NpdGl2ZSBza2V3bmVzcywgd2hpY2ggbWVhbnMgdGhhdCB0aGVzZSB2YXJpYWJsZXMgc2tldyBoZWF2aWx5IHRvIHRoZSBsZWZ0LiBUaGlzIGNhbiBiZSB2ZXJpZmllZCBieSBsb29raW5nIGF0IHRoZSBoaXN0b2dyYW1zIG9mIHRoZXNlIHZhcmlhYmxlcy4KCldlIGFsc28gbm90ZSB0aGF0IHRoZSA3NXRoIHBlcmNlbnRpbGUgb2YgZWFjaCB2YXJpYWJsZSwgYXBhcnQgZnJvbSBhdmVyYWdlX3JhdGluZ3MsIGlzIHJlbGF0aXZlbHkgdmVyeSBzbWFsbCBjb21wYXJpbmcgdG8gdGhlIG1heGltdW0sIHdoaWNoIGNhbiBhbHNvIGV4cGxhaW4gdGhlIHBvc2l0aXZlIHNrZXduZXNzIG9mIHRob3NlIHZhcmlhYmxlcy4KCkFsbCA0IHZhcmlhYmxlcyBoYXZlIGEgcG9zaXRpdmUga3VydG9zaXMsIHdoaWNoIHNob3dzIHRoYXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGVzZSB2YXJpYWJsZXMgaGF2ZSBoZWF2aWVyIHRhaWxzIHRoYW4gdGhlIG5vcm1hbCBkaXN0cmlidXRpb24gd2l0aCB0aGUgc2FtZSBtZWFuIGFuZCBzdGFuZGFyZCBkZXZpYXRpb24uIAoKRnJvbSB0aGUgcHJvcGVydGllcyB3aXRuZXNzZWQgYWJvdmUsIHRvIGJldHRlciB1bmRlcnN0YW5kIHRoZSBkaXN0cmlidXRpb24gb2YgdmFyaWFibGVzIHdpdGhpbiBzcGVjaWZpYyByYW5nZXMsIHdlIGFnYWluIGdlbmVyYXRlIGhpc3RvZ3JhbXMgZm9yIHR3byB2YXJpYWJsZXMsIHJhdGluZ3NfY291bnQgYW5kIHRleHRfcmV2aWV3c19jb3VudC4gVGhpcyB0aW1lLCB3ZSB3aWxsIHNldCB0aGUgbGltaXQgb2YgdGhlIHgtYXhpcyB0byBiZSAxMDAgZm9yIG51bV9wYWdlcywgNTAwMCBmb3IgcmF0aW5nc19jb3VudCBhbmQgNTAwIGZvciB0ZXh0X3Jldmlld3NfY291bnQuCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBnb29kcmVhZHMsIGFlcyh4ID0gZ29vZHJlYWRzJG51bV9wYWdlcykpICsgCiAgICBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9IC4uZGVuc2l0eS4uKSxiaW53aWR0aCA9IDIwLCBjb2xvcj0nb3JhbmdlJywgZmlsbD0nb3JhbmdlJykgKwogICAgeGxpbShjKDAsMTAwMCkpICsgCiAgICBnZW9tX2RlbnNpdHkoY29sb3VyPSJibHVlIiwgbHdkID0gMC41LCBhbHBoYT0wLjUpICsgCiAgICBsYWJzKHRpdGxlPSJIaXN0b2dyYW0gZm9yIGJvb2tzJyBudW1iZXIgb2YgcGFnZXMiLCB4ID0gIk51bWJlciBvZiBwYWdlcyIsIHkgPSAiTnVtYmVyIG9mIGJvb2tzIikgKyAKICAgIGdlb21fdmxpbmUoZGF0YSA9IGdvb2RyZWFkcywgeGludGVyY2VwdCA9IG1lYW4oZ29vZHJlYWRzJGF2ZXJhZ2VfcmF0aW5nLCBuYS5ybSA9IFRSVUUpLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMC41KQoKZ2dwbG90KGRhdGEgPSBnb29kcmVhZHMsIGFlcyh4ID0gZ29vZHJlYWRzJHJhdGluZ3NfY291bnQpKSArIAogICAgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLiksYmlud2lkdGggPSAxMCwgY29sb3I9J29yYW5nZScsIGZpbGw9J29yYW5nZScpICsKICAgIHhsaW0oYygwLDUwMDApKSArIAogICAgZ2VvbV9kZW5zaXR5KGNvbG91cj0iYmx1ZSIsIGx3ZCA9IDAuNSwgYWxwaGE9MC41KSArIAogICAgbGFicyh0aXRsZT0iSGlzdG9ncmFtIGZvciByYXRpbmdzIGNvdW50IG9mIGVhY2ggYm9vay9zZXJpZXMiLCB4ID0gIlJhdGluZ3MgY291bnQiLCB5ID0gIk51bWJlciBvZiBib29rcyIpICsgCiAgICBnZW9tX3ZsaW5lKGRhdGEgPSBnb29kcmVhZHMsIHhpbnRlcmNlcHQgPSBtZWFuKGdvb2RyZWFkcyRhdmVyYWdlX3JhdGluZywgbmEucm0gPSBUUlVFKSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDAuNSkKCmdncGxvdChkYXRhID0gZ29vZHJlYWRzLCBhZXMoeCA9IGdvb2RyZWFkcyR0ZXh0X3Jldmlld3NfY291bnQpKSArIAogICAgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLiksYmlud2lkdGggPSAxICwgY29sb3I9J29yYW5nZScsIGZpbGw9J29yYW5nZScpICsKICAgIHhsaW0oYygwLDUwMCkpICsgCiAgICBnZW9tX2RlbnNpdHkoY29sb3VyPSJibHVlIiwgbHdkID0gMC41LCBhbHBoYT0wLjUpICsgCiAgICBsYWJzKHRpdGxlPSJIaXN0b2dyYW0gZm9yIHRleHQgcmV2aWV3cyBjb3VudCBmb3IgZWFjaCBib29rL3NlcmllcyIsIHggPSAiVGV4dCByZXZpZXdzIGNvdW50IiwgeSA9ICJOdW1iZXIgb2YgYm9va3MiKSArIAogICAgZ2VvbV92bGluZShkYXRhID0gZ29vZHJlYWRzLCB4aW50ZXJjZXB0ID0gbWVhbihnb29kcmVhZHMkYXZlcmFnZV9yYXRpbmcsIG5hLnJtID0gVFJVRSksIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIsIHNpemUgPSAwLjUpCmBgYAoKQWZ0ZXIgcGxvdHRpbmcgdGhlIGhpc3RvZ3JhbXMgb2YgdGhyZWUgdmFyaWFibGVzIGFnYWluLCB3ZSBzZWUgdGhhdCB0aGUgcmF0aW5nc19jb3VudCBhbmQgdGV4dF9yZXZpZXdzX2NvdW50IHN0aWxsIHNrZXcgaGVhdmlseSB0byB0aGUgbGVmdC4gVGhlIG1vZGUgb2YgdGhlc2UgdHdvIHZhcmlhYmxlcyAwIGFuZCAzLCByZXNwZWN0aXZlbHksIHdoaWNoIGFsc28gYWRkIHRvIHRoZSBmYWN0IHRoYXQgbW9zdCBib29rcyByZWNlaXZlIHZlcnkgZmV3IHJhdGluZ3MvcmV2aWV3cy4gCgpDb25zaWRlcmluZyBudW1iZXIgb2YgcGFnZXMsIG1vc3QgYm9va3MgaGF2ZSB0aGUgbnVtYmVyIG9mIHBhZ2VzIG9mIGFyb3VuZCAxNzUgLSAzMjUuIFRoZW4sIHRoZSBudW1iZXIgb2YgYm9vayBpbnZlcnNlbHkgcHJvcG9ydGlvbmFsIHRvIHRoZSBudW1iZXIgb2YgcGFnZXMgKGkuZSB0aGUgbnVtYmVyIG9mIGJvb2tzIGlzIGRlY3JlYXNpbmcgd2l0aCByZXNwZWN0IHRvIHRoZWlyIGxlbmd0aCkuCgojI1NjYXR0ZXJwbG90IG9mIHBhaXJzIG9mIG51bWVyaWNhbCB2YXJpYWJsZXMgaW4gdGhlIGRhdGFzZXQKCmBgYHtyfQpnZ3BhaXJzKHdpdGgoZ29vZHJlYWRzLCBkYXRhLmZyYW1lKGF2ZXJhZ2VfcmF0aW5nLCBudW1fcGFnZXMsIHJhdGluZ3NfY291bnQsIHRleHRfcmV2aWV3c19jb3VudCkpKQpgYGAKCkZyb20gdGhpcyBzY2F0dGVycGxvdCBtYXRyaXgsIHdlIGNhbiBzZWUgdGhhdCwgYXBhcnQgZnJvbSB0aGUgcGFpcnMgcmF0aW5nc19jb3VudCBhbmQgdGV4dF9yZXZpZXdzX2NvdW50LCB0aGUgcmVtYWluaW5nIHNob3dzIGxpdHRsZSB0byBubyBjb3JyZWxhdGlvbi4gQWxsIG9mIHRoZSBzY2F0dGVycGxvdCBzaG93cyBhIG5lYXJseSB2ZXJ0aWNhbCB0cmVuZCBsaW5lIChpLmUgdGhlcmUgaXMgbm8gbGluZWFyIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIHR3byB2YXJpYWJsZXMgcGxvdHRlZCkuIAoKIyNSYXRpbmcgZGlzdHJpYnVzdGlvbiBvZiBib29rcyBpbiB0aGUgZGF0YXNldAoKRmlyc3QsIHdlIGRpdmlkZSB0aGUgcmF0aW5ncyBpbnRvIGZpdmUgZGlmZmVyZW50IHJhbmdlczoKCiAgKiBCZXR3ZWVuIDAgYW5kIDEKICAqIEJldHdlZW4gMSBhbmQgMgogICogQmV0d2VlbiAyIGFuZCAzCiAgKiBCZXR3ZWVuIDMgYW5kIDQKICAqIEJldHdlZW4gNCBhbmQgNSAKICAKV2UgY2FsY3VsYXRlIHRoZSBudW1iZXIgb2YgYm9va3Mgd2l0aGluIGVhY2ggcmFuZ2UsIGFsb25nIHdpdGggdGhlaXIgcGVyY2VudGFnZXMuCgpgYGB7cn0KcmF0aW5nX2Rpc3RyaWJ1dGlvbl9nb29kcmVhZHMgPC0gZGF0YS5mcmFtZShtYXRyaXgoMCwgbmNvbCA9IDMsIG5yb3cgPSA1KSkKbmFtZXMocmF0aW5nX2Rpc3RyaWJ1dGlvbl9nb29kcmVhZHMpIDwtIGMoJ1JhbmdlJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnbnVtYmVyX29mX2Jvb2tzJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAncGVyY2VudGFnZScpCgpmb3IoaSBpbiBjKDE6NSkpIHsKICByYXRpbmdfZGlzdHJpYnV0aW9uX2dvb2RyZWFkc1tpLDFdIDwtIHBhc3RlKCdCZXR3ZWVuJyxpLTEsJ2FuZCcsaSkKfQoKZm9yKGkgaW4gYygxOm5yb3coZ29vZHJlYWRzKSkpIHsKICBpZiAoZ29vZHJlYWRzW2ksNF0gPj0gMCAmIGdvb2RyZWFkc1tpLDRdIDwgMSkgewogICAgcmF0aW5nX2Rpc3RyaWJ1dGlvbl9nb29kcmVhZHNbMSwyXSA9IHJhdGluZ19kaXN0cmlidXRpb25fZ29vZHJlYWRzWzEsMl0gKyAxIAogIH0KICBpZiAoZ29vZHJlYWRzW2ksNF0gPj0gMSAmIGdvb2RyZWFkc1tpLDRdIDwgMikgewogICAgcmF0aW5nX2Rpc3RyaWJ1dGlvbl9nb29kcmVhZHNbMiwyXSA9IHJhdGluZ19kaXN0cmlidXRpb25fZ29vZHJlYWRzWzIsMl0gKyAxIAogICAgfQogIGlmIChnb29kcmVhZHNbaSw0XSA+PSAyICYgZ29vZHJlYWRzW2ksNF0gPCAzKSB7CiAgICByYXRpbmdfZGlzdHJpYnV0aW9uX2dvb2RyZWFkc1szLDJdID0gcmF0aW5nX2Rpc3RyaWJ1dGlvbl9nb29kcmVhZHNbMywyXSArIDEgCiAgICB9CiAgaWYgKGdvb2RyZWFkc1tpLDRdID49IDMgJiBnb29kcmVhZHNbaSw0XSA8IDQpIHsKICAgIHJhdGluZ19kaXN0cmlidXRpb25fZ29vZHJlYWRzWzQsMl0gPSByYXRpbmdfZGlzdHJpYnV0aW9uX2dvb2RyZWFkc1s0LDJdICsgMSAKICAgIH0KICBpZiAoZ29vZHJlYWRzW2ksNF0gPj0gNCAmIGdvb2RyZWFkc1tpLDRdIDwgNSkgewogICAgcmF0aW5nX2Rpc3RyaWJ1dGlvbl9nb29kcmVhZHNbNSwyXSA9IHJhdGluZ19kaXN0cmlidXRpb25fZ29vZHJlYWRzWzUsMl0gKyAxIAogIH0KfQoKcmF0aW5nX2Rpc3RyaWJ1dGlvbl9nb29kcmVhZHMkcGVyY2VudGFnZSA8LSByb3VuZCgocmF0aW5nX2Rpc3RyaWJ1dGlvbl9nb29kcmVhZHMkbnVtYmVyX29mX2Jvb2tzKS9ucm93KGdvb2RyZWFkcykqMTAwLGRpZ2l0cyA9IDIpCgpyYXRpbmdfZGlzdHJpYnV0aW9uX2dvb2RyZWFkcwpgYGAKClRoZW4sIHdlIHdpbGwgcGxvdCB0d28gZGlmZmVyZW50IHBpZSBjaGFydHMsIGEgdHJhZGl0aW9uYWwgb25lIGFuZCBhIHNxdWFyZSBwaWUgY2hhcnQsIHRvIG9ic2VydmUgdGhlIGRpc3RyaWJ1dGlvbiBvZiBib29rcyB3aXRoaW4gcmFuZ2VzLgoKYGBge3J9CmdncGxvdChkYXRhPXJhdGluZ19kaXN0cmlidXRpb25fZ29vZHJlYWRzLCBhZXMoeD0iIiwgeT1wZXJjZW50YWdlLAogICAgICAgZmlsbCA9IGZhY3RvcihyYXRpbmdfZGlzdHJpYnV0aW9uX2dvb2RyZWFkc1ssMV0pLCApKSArCiAgICAgICBnZW9tX2Jhcih3aWR0aCA9IDEsIHN0YXQgPSAiaWRlbnRpdHkiKSArIAogICAgICAgZ2d0aXRsZSgiUGllIGNoYXJ0IG9mIHBlcmNlbnRhZ2Ugb2YgQm9va3Mgd2l0aCByZXNwZWN0IHRvIHJhdGluZ3MgY291bnQiKSArIAogICAgICAgY29vcmRfcG9sYXIodGhldGE9InkiLCBzdGFydCA9IDApICsKICAgICAgIGxhYnMoZmlsbCA9IGZhY3RvcihyYXRpbmdfZGlzdHJpYnV0aW9uX2dvb2RyZWFkc1ssMV0pKQoKcmF0aW5nX2Rpc3RyaWJ1dGlvbl9udW1iZXIgPC0gYyhgQmV0d2VlbiAwIGFuZCAxYD0gMC4yNSwgYEJldHdlZW4gMSBhbmQgMmA9IDAuMDIsIGBCZXR3ZWVuIDIgYW5kIDNgPSAwLjUzLCBgQmV0d2VlbiAzIGFuZCA0YD0gNTUuMzIsIGBCZXR3ZWVuIDQgYW5kIDVgPSA0My42OSkKCndhZmZsZShyYXRpbmdfZGlzdHJpYnV0aW9uX251bWJlciwgcm93cz02LCAKICAgICAgIGNvbG9ycz1icmV3ZXIucGFsKDUsIlNldDEiKSwKICAgICAgIHRpdGxlPSJQZXJjZW50YWdlIG9mIEJvb2tzIHdpdGggcmVzcGVjdCB0byByYXRpbmdzIGNvdW50IikKYGBgCgpXZSBjYW4gc2VlIHRoYXQsIG1vcmUgdGhhbiBoYWxmIG9mIHRoZSBib29rcyBhcmUgcmF0ZWQgYmV0d2VlbiAzIGFuZCA0IHBvaW50cywgd2hpbGUgbW9zdCB0aGUgcmVtYWluaW5nIGJvb2tzIGFyZSByYW5rZWQgYmV0d2VlbiA0IGFuZCA1LiBUaGVyZSBhcmUgYSB0b3RhbCBvZiAxMzU3NyBib29rcyByYXRlZCBiZXR3ZWVuIDMgYW5kIDUsIHdoaWNoIGNvdW50cyB1cCB0byA5OSUgb2YgdGhlIGJvb2tzIGluIHRoZSBkYXRhc2V0LgoKIyNCb29rcyB3aXRoIHRoZSBtb3N0IG9jY3VyZW5jZXMgaW4gdGhlIGRhdGFzZXQKCmBgYHtyfQpib29rX2ZyZXF1ZW5jeSA8LSBkYXRhLmZyYW1lKHRhYmxlKGdvb2RyZWFkcyR0aXRsZSksIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKbmFtZXMoYm9va19mcmVxdWVuY3kpIDwtIGMoJ3RpdGxlJywnZnJlcXVlbmN5JykKYm9va19mcmVxdWVuY3kgPC0gYm9va19mcmVxdWVuY3lbb3JkZXIoLWJvb2tfZnJlcXVlbmN5JGZyZXF1ZW5jeSksXSAKaGVhZChib29rX2ZyZXF1ZW5jeSkKCmJvb2tfZnJlcXVlbmN5X3RvcDIwIDwtIGJvb2tfZnJlcXVlbmN5W2MoMToyMCksXQpnZ3Bsb3QoZGF0YT1ib29rX2ZyZXF1ZW5jeV90b3AyMCwgYWVzKHg9cmVvcmRlcih0aXRsZSwgZnJlcXVlbmN5KSx5PWZyZXF1ZW5jeSwgZmlsbCA9IGZhY3RvcihjKDE6bnJvdyhib29rX2ZyZXF1ZW5jeV90b3AyMCkpKSkpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgY29vcmRfZmxpcCgpICsKICBnZ3RpdGxlKCcyMCBib29rcyB3aXRoIGhpZ2hlc3QgZnJlcXVlbmN5JykgKyAKICBsYWJzKHggPSAiVGl0bGUiLCB5ID0gIkZyZXF1ZW5jeSIpIApgYGAKCkFib3ZlLCB3ZSBoYXZlIHBsb3R0ZWQgdGhlIDIwIGJvb2tzIHdpdGggdGhlIG1vc3QgYXBwZWFyYW5jZXMgaW4gdGhlIGRhdGFzZXQuIFdlIGNhbiBzZWUgdGhhdCAiT25lIEh1bmRyZWQgWWVhciBvZiBTb2xpdHVkZSIgYW5kICJTYWxlbSdzIExvdCIgYXJlIHRoZSB0d28gYm9va3MgYXBwZWFyaW5nIHRoZSBtb3N0LCB3aXRoIDExIHRpbWVzIGZvciBlYWNoIGJvb2suCgpUaGVzZSBib29rcyBhcmUgcHVibGlzaGVkIGFnYWluIGFuZCBhZ2FpbiwgZWFjaCB0aW1lIHdpdGggYW5vdGhlciBwdWJsaXNoZXIuIFRoaXMgc2hvd3MgdGhhdCB0aGVzZSBhcmUgc3RpbGwgaW4gZ3JlYXQgZGVtYW5kLCBkZXNwaXRlIHRoZSBmbG93IG9mIHRpbWUuCgojI0xhbmd1YWdlcyB3cml0dGVuIGluIGJvb2tzL3NlcmllcwoKYGBge3J9CmJvb2tfbGFuZ3VhZ2UgPC0gZGF0YS5mcmFtZSh0YWJsZShnb29kcmVhZHMkbGFuZ3VhZ2VfY29kZSksIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKbmFtZXMoYm9va19sYW5ndWFnZSkgPC0gYygnbGFuZ3VhZ2UnLCdmcmVxdWVuY3knKQpib29rX2xhbmd1YWdlIDwtIGJvb2tfbGFuZ3VhZ2Vbb3JkZXIoLWJvb2tfbGFuZ3VhZ2UkZnJlcXVlbmN5KSxdCmJvb2tfbGFuZ3VhZ2UkcGVyY2VudGFnZSA8LSByb3VuZChib29rX2xhbmd1YWdlJGZyZXF1ZW5jeS9ucm93KGdvb2RyZWFkcykqMTAwLCBkaWdpdHMgPSAyKQpoZWFkKGJvb2tfbGFuZ3VhZ2UpCgpnZ3Bsb3QoYm9va19sYW5ndWFnZSwgYWVzKHg9cmVvcmRlcihsYW5ndWFnZSwtZnJlcXVlbmN5KSx5PWZyZXF1ZW5jeSwgZmlsbCA9IGZhY3RvcihjKDE6bnJvdyhib29rX2xhbmd1YWdlKSkpKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKyAKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICBnZ3RpdGxlKCdMYW5ndWFnZXMgd3JpdHRlbiBpbiBib29rcy9zZXJpZXMnKSArIAogIGxhYnMoeCA9ICJMYW5ndWFnZSIsIHkgPSAiVG90YWwgbnVtYmVyIG9mIGJvb2tzIikgKwogIGdlb21fdGV4dChhZXMobGFiZWw9ZnJlcXVlbmN5KSwgdmp1c3Q9LTAuNSkKYGBgCgpXZSBjYW4gZWFzaWx5IHNlZSB0aGF0IG1vc3Qgb2YgdGhlIGJvb2tzIGluIHRoaXMgZGF0YXNldCBpcyB3cml0dGVuIGluIEVuZ2xpc2guIFR3byBtb3N0IHVzZWQgbGFuZ3VhZ2VzIGFyZSBFbmdsaXNoIGFuZCBFbmdsaXNoLVVTLCB3aGlsZSB3aXRoaW4gNiBtb3N0IGNvbW1vbmx5IHVzZWQgbGFuZ3VhZ2VzLCBFbmdsaXNoIGFuZCBpdHMgZGlmZmVyZW50IHZlcnNpb25zIChlbmctVVMgYW5kIGVuZy1HQikgY291bnRzIHVwIHRvIGhhbGYgb2YgdGhlbS4gQ29tYmluaW5nIGFsbCB0aGUgZGlmZmVyZW50IHZlcnNpb25zIG9mIHRoaXMgbGFuZ3VhZ2UsIEVuZ2xpc2ggaXMgd3JpdHRlbiBpbiAxMjYzNCBib29rcywgd2hpY2ggaXMgOTIsMTIlIG9mIGFsbCB0aGUgYm9va3MuIEFwYXJ0IGZyb20gRW5nbGlzaCwgU3BhbmlzaCwgdGhlIHRoaXJkIGNvbW1vbmx5IHVzZWQgbGFuZ3VhZ2UsIG9ubHkgaGFzIDQxOSBib29rcywgd2hpY2ggaXMgMy4xJSBvZiB0aGUgYm9va3MgaW4gdGhpcyBkYXRhc2V0LiAKCiMxMCBCb29rcyB3aXRoIHRoZSBoaWdoZXN0IGF2ZXJhZ2UgcmF0aW5nCgpgYGB7cn0KYm9va19hdmVyYWdlX3JhdGluZyA8LSBnb29kcmVhZHNbb3JkZXIoLWdvb2RyZWFkcyRhdmVyYWdlX3JhdGluZyksXQoKYm9va19hdmVyYWdlX3JhdGluZ190b3AxMCA8LSBib29rX2F2ZXJhZ2VfcmF0aW5nW2MoMToxMCksXQpib29rX2F2ZXJhZ2VfcmF0aW5nX3RvcDEwCmdncGxvdChkYXRhPWJvb2tfYXZlcmFnZV9yYXRpbmdfdG9wMTAsIGFlcyh4PXJlb3JkZXIodGl0bGUsIGF2ZXJhZ2VfcmF0aW5nKSx5PWF2ZXJhZ2VfcmF0aW5nLCBmaWxsID0gZmFjdG9yKGMoMTpucm93KGJvb2tfYXZlcmFnZV9yYXRpbmdfdG9wMTApKSkpKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArIAogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKwogIGNvb3JkX2ZsaXAoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZT0iUGFpcmVkIikgKyAKICBnZ3RpdGxlKCdCb29rcy9zZXJpZXMgd2l0aCBoaWdoZXN0IGF2ZXJhZ2UgcmF0aW5nJykgKyAKICBsYWJzKHggPSAiQm9vay9zZXJpZXMiLCB5ID0gIkF2ZXJhZ2UgcmF0aW5nIikgKwogIGdlb21fdGV4dCgKICAgIGFlcyhsYWJlbCA9IGF2ZXJhZ2VfcmF0aW5nKSwgY29sb3VyID0gImJsYWNrIiwKICAgIGhqdXN0ID0gLTAuMSwgc2l6ZSA9IDIsCiAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gMSksCiAgICBpbmhlcml0LmFlcyA9IFRSVUUKICApCgphdXRob3JfaGlnaGVzdF9ib29rX2F2ZXJhZ2VfcmF0aW5nIDwtIGRhdGEuZnJhbWUodGFibGUoYm9va19hdmVyYWdlX3JhdGluZ190b3AxMCRhdXRob3JzKSkKbmFtZXMoYXV0aG9yX2hpZ2hlc3RfYm9va19hdmVyYWdlX3JhdGluZykgPC0gYygnYXV0aG9yJywgJ251bWJlcl9vZl9ib29rcycpCmF1dGhvcl9oaWdoZXN0X2Jvb2tfYXZlcmFnZV9yYXRpbmcgPC0gYXV0aG9yX2hpZ2hlc3RfYm9va19hdmVyYWdlX3JhdGluZ1tvcmRlcigtYXV0aG9yX2hpZ2hlc3RfYm9va19hdmVyYWdlX3JhdGluZyRudW1iZXJfb2ZfYm9va3MpLF0KYXV0aG9yX2hpZ2hlc3RfYm9va19hdmVyYWdlX3JhdGluZwpgYGAKClN1cnByaXNpbmdseSwgYWxsIDEwIG9mIHRoZSBoaWdoZXN0IHJhdGluZyBib29rcyBoYXZlIHRoZSBtYXhpbXVtIHJhdGluZywgNS4gQXMgYSByZXN1bHQsIHdlIHdpbGwgaW52ZXN0aWdhdGUgYWxsIHRoZSBib29rcyB0aGF0IGhhdmUgdGhlIHJhdGluZyBvZiA1IGluIHRoaXMgZGF0YXNldC4KCmBgYHtyfQpib29rc19yYXRpbmdfNSA8LSBnb29kcmVhZHNbZ29vZHJlYWRzJGF2ZXJhZ2VfcmF0aW5nID09IDUsXQpib29rc19yYXRpbmdfNSA8LSBib29rc19yYXRpbmdfNVtvcmRlcigtYm9va3NfcmF0aW5nXzUkcmF0aW5nc19jb3VudCksXQpib29rc19yYXRpbmdfNSAKCnByaW50KHBhc3RlKCdUaGVyZSBhcmUnLG5yb3coYm9va3NfcmF0aW5nXzUpLCdib29rcyB0aGF0IHJlY2VpdmUgYW4gYXZlcmFnZSByYXRpbmcgb2YgNS4nKSkKYGBgCgpXZSBjYW4gZWFzaWx5IHNlZSB0aGF0IGFsbCBvZiB0aGUgYm9va3Mgd2l0aCBhdmVyYWdlIHJhdGluZ3Mgb2YgNSByZWNlaXZlIHZlcnkgZmV3IHJhdGluZ3MuIFRoZSBtYXhpbXVtIHJhdGluZ3MgY291bnQgYW1vbmcgdGhlc2UgYm9va3MgYXJlIDUuIFNwZWNpZmljYWxseSwgdGhlcmUgYXJlIDQgYm9va3MgdGhhdCByZWNlaXZlIDAgcmF0aW5ncywgYW5kIHN0aWxsIHJlY2VpdmUgYW4gYXZlcmFnZSByYXRpbmcgb2YgNSwgd2hpY2ggaXMgbm90IHN1cHBvc2VkIHRvIGhhcHBlbi4gTW9yZW92ZXIsIDIzLzI4IGJvb2tzIGluIHRoZSBsaXN0IHJlY2VpdmUgMCB0ZXh0IHJldmlld3MuIFdlIGNhbiBub3QgY29uc2lkZXIgdGhlc2UgYm9va3MgdG8gYmUgdGhlIG9uZXMgd2l0aCBoaWdoZXN0IGF2ZXJhZ2UgcmF0aW5nLCBhcyB0aGUgcmF0aW5ncyBjb3VudCBpcyB0b28gbG93LgoKSW5zdGVhZCwgd2UgcHJvcG9zZSB0aGUgZm9sbG93aW5nIG1ldGhvZDogV2Ugb25seSBjb25zaWRlciB0aGUgYXZlcmFnZSByYXRpbmcgb2YgYm9va3Mgd2l0aCBtb3JlIHRoYW4gMTAwMCByYXRpbmdzLiAKCmBgYHtyfQpib29rXzEwMDBfcmF0aW5nc19jb3VudCA8LSBnb29kcmVhZHNbZ29vZHJlYWRzJHJhdGluZ3NfY291bnQgPj0gMTAwMCxdCmJvb2tfMTAwMF9yYXRpbmdzX2NvdW50IDwtIGJvb2tfMTAwMF9yYXRpbmdzX2NvdW50W29yZGVyKC1ib29rXzEwMDBfcmF0aW5nc19jb3VudCRhdmVyYWdlX3JhdGluZyksXQpib29rXzEwMDBfcmF0aW5nc19jb3VudAoKYm9va18xMDAwX3JhdGluZ3NfY291bnRfdG9wMTAgPC0gYm9va18xMDAwX3JhdGluZ3NfY291bnRbYygxOjEwKSxdCmJvb2tfMTAwMF9yYXRpbmdzX2NvdW50X3RvcDEwCmdncGxvdChkYXRhPWJvb2tfMTAwMF9yYXRpbmdzX2NvdW50X3RvcDEwLCBhZXMoeD1yZW9yZGVyKHRpdGxlLCBhdmVyYWdlX3JhdGluZykseT1hdmVyYWdlX3JhdGluZywgZmlsbCA9IGZhY3RvcihjKDE6bnJvdyhib29rXzEwMDBfcmF0aW5nc19jb3VudF90b3AxMCkpKSkpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgY29vcmRfZmxpcCgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJQYWlyZWQiKSArIAogIGdndGl0bGUoJ0Jvb2tzL3NlcmllcyB3aXRoIGhpZ2hlc3QgYXZlcmFnZSByYXRpbmcgKHJhdGluZ3MgY291bnQgPj0gMTAwMCknKSArIAogIGxhYnMoeCA9ICJCb29rL3NlcmllcyIsIHkgPSAiQXZlcmFnZSByYXRpbmciKSArCiAgZ2VvbV90ZXh0KAogICAgYWVzKGxhYmVsID0gYXZlcmFnZV9yYXRpbmcpLCBjb2xvdXIgPSAiYmxhY2siLAogICAgaGp1c3QgPSAtMC4xLCBzaXplID0gMiwKICAgIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAxKSwKICAgIGluaGVyaXQuYWVzID0gVFJVRQogICkKCmF1dGhvcl9ib29rXzEwMDBfcmF0aW5nc19jb3VudCA8LSBkYXRhLmZyYW1lKHRhYmxlKGJvb2tfMTAwMF9yYXRpbmdzX2NvdW50X3RvcDEwJGF1dGhvcnMpKQpuYW1lcyhhdXRob3JfYm9va18xMDAwX3JhdGluZ3NfY291bnQpIDwtIGMoJ2F1dGhvcicsICdudW1iZXJfb2ZfYm9va3MnKQphdXRob3JfYm9va18xMDAwX3JhdGluZ3NfY291bnQgPC0gYXV0aG9yX2Jvb2tfMTAwMF9yYXRpbmdzX2NvdW50W29yZGVyKC1hdXRob3JfYm9va18xMDAwX3JhdGluZ3NfY291bnQkbnVtYmVyX29mX2Jvb2tzKSxdCmF1dGhvcl9ib29rXzEwMDBfcmF0aW5nc19jb3VudApgYGAKCldlIGNhbiBzZWUgdGhhdCwgdGhlcmUgaXMgb25seSA2MDU5IGJvb2tzIHRoYXQgaGF2ZSB0aGUgcmF0aW5ncyBjb3VudCBvZiBtb3JlIHRoYW4gb3IgZXF1YWwgdG8gMTAwMCwgd2hpY2ggYWNjb3VudHMgZm9yIDQ0LjE4JSBvZiBhbGwgdGhlIGJvb2tzIGluIHRoZSBkYXRhc2V0LiAKCldpdGhpbiB0aGVzZSA2MDU5IGJvb2tzLCB0aGUgaGlnaGVzdCBoYXMgdGhlIGF2ZXJhZ2UgcmF0aW5nIG9mIDQuODIsIHdoaWxlIHRoZSBsb3dlc3QgcmF0aW5nIGFtb25nIHRoZSB0b3AgMTAgYm9va3MgaXMgNC43LiAKCkFtb25nIHRob3NlIDEwIGJvb2tzLCBoYWxmIG9mIHRoZW0gYXJlIHdyaXR0ZW4gYnkgQmlsbCBXYXR0ZXJzb24sIHR3byBhcmUgZnJvbSBKLksuIFJvd2xpbmcsIG9uZSBmcm9tIFBhdHJpY2sgTydCcmlhbiwgb25lIGZyb20gSm95Y2UgTWV5ZXIgYW5kIG9uZSBmcm9tIGFub255bW91cy4gV2UgY2FuIGVhc2lseSBzZWUgdGhhdCBCaWxsIFdhdHRlcnNvbiBoYXMgd3JpdHRlbiBib29rcyB0aGF0IHJlY2VpdmUgdmVyeSBoaWdoIHJhdGluZ3MgZnJvbSBhIGxhcmdlIG51bWJlciBvZiByZWFkZXJzLiBUd28gYW1vbmcgdGhlIHRvcCAxMCBmcm9tIEouSyBSb3dsaW5nIGFyZSBib3RoIHNldHMgb2YgSGFycnkgUG90dGVyIChvbmUgc2V0IG9mICMxLTUgYW5kIG9uZSBzZXQgb2YgIzEtNikuIAoKQW5vdGhlciBpbnRlcmVzdGluZyBmYWN0IGlzIHRoYXQgYWxsIGJvb2tzIGluIHRvcCAxMCBhcmUgd3JpdHRlbiBpbiBFbmdsaXNoLCB3aXRoIG9uZSBzcGVjaWZpY2FsbHkgd3JpdHRlbiBpbiBlbi1VUy4KCiMxMCBsb25nZXN0IGJvb2tzIGluIHRoZSBkYXRhc2V0CgpgYGB7cn0KYm9va19udW1fcGFnZXMgPC0gZ29vZHJlYWRzW29yZGVyKC1nb29kcmVhZHMkbnVtX3BhZ2VzKSxdCgpib29rX251bV9wYWdlc190b3AxMCA8LSBib29rX251bV9wYWdlc1tjKDE6MTApLF0KYm9va19udW1fcGFnZXNfdG9wMTAKZ2dwbG90KGRhdGE9Ym9va19udW1fcGFnZXNfdG9wMTAsIGFlcyh4PXJlb3JkZXIodGl0bGUsIG51bV9wYWdlcykseT1udW1fcGFnZXMsIGZpbGwgPSBmYWN0b3IoYygxOm5yb3coYm9va19udW1fcGFnZXNfdG9wMTApKSkpKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArIAogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKwogIGNvb3JkX2ZsaXAoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZT0iUGFpcmVkIikgKyAKICBnZ3RpdGxlKCdCb29rcy9zZXJpZXMgd2l0aCBoaWdoZXN0IG51bWJlciBvZiBwYWdlcycpICsgCiAgbGFicyh4ID0gIkJvb2svc2VyaWVzIiwgeSA9ICJOdW1iZXIgb2YgcGFnZXMiKSArCiAgZ2VvbV90ZXh0KAogICAgYWVzKGxhYmVsID0gbnVtX3BhZ2VzKSwgY29sb3VyID0gImJsYWNrIiwKICAgIGhqdXN0ID0gLTAuMSwgc2l6ZSA9IDIsCiAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gMSksCiAgICBpbmhlcml0LmFlcyA9IFRSVUUKICApCgphdXRob3JfYm9va19udW1fcGFnZXMgPC0gZGF0YS5mcmFtZSh0YWJsZShib29rX251bV9wYWdlc190b3AxMCRhdXRob3JzKSkKbmFtZXMoYXV0aG9yX2Jvb2tfbnVtX3BhZ2VzKSA8LSBjKCdhdXRob3InLCAnbnVtYmVyX29mX2Jvb2tzJykKYXV0aG9yX2Jvb2tfbnVtX3BhZ2VzIDwtIGF1dGhvcl9ib29rX251bV9wYWdlc1tvcmRlcigtYXV0aG9yX2Jvb2tfbnVtX3BhZ2VzJG51bWJlcl9vZl9ib29rcyksXQphdXRob3JfYm9va19udW1fcGFnZXMKYGBgCgpXZSBjYW4gZWFzaWx5IHNlZSB0aGF0LCBhbGwgb2YgdGhlIDEwIGxvbmdlc3QgaW4gdGhpcyBkYXRhc2V0IGFyZSBhbGwgc2VyaWVzLiBBbW9uZyB0aGVtLCAiVGhlIENvbXBsZXQgQXVicmV5L01hdHVyaW4gTm92ZWxzIiBzdGFuZHMgb3V0IGZyb20gdGhlIGNyb3dkLCB3aXRoIGl0cyBsZW5ndGggb2YgbmVhcmx5IDMgdGltZXMgdGhlIGxlbmd0aCBvZiB0aGUgMTB0aCBsb25nZXN0IHNlcmllcywgIkNpdmlsIFdhcjogYSBOYXJyYXRpdmUiLiBNb3Jlb3ZlciwgIlRoZSBOb3J0b24gQW50aG9sb2d5IG9mIEVuZ2xpc2ggTGl0ZXJhdHVyZSIgaGFzIDMgb2YgdGhlaXIgdm9sdW1ucyBwcmVzZW50aW5nIGluIHRoZSB0b3AgMTAgKFZvbHMgQS1DLCBWb2wgMiBhbmQgVm9sIDEpLiBIb3dldmVyLCB0aGVpciBhcHBlYXJhbmNlcyBhcmUgbm90IGNvbnNpc3RlbnQ6IFZvbHMgQS1DIGFuZCBWb2wgMSBoYXZlIHRoZSBzYW1lIG5hbWUsIGJ1dCB0aGVpciBudW1iZXIgb2YgcGFnZXMgYXJlIGRpZmZlcmVudC4KCiMjMTAgQm9va3Mgd2l0aCB0aGUgaGlnaGVzdCBudW1iZXIgb2YgcmF0aW5ncyBjb3VudCAKCmBgYHtyfQpib29rX3JhdGluZ3NfY291bnQgPC0gZ29vZHJlYWRzW29yZGVyKC1nb29kcmVhZHMkcmF0aW5nc19jb3VudCksXQoKYm9va19yYXRpbmdzX2NvdW50X3RvcDEwIDwtIGJvb2tfcmF0aW5nc19jb3VudFtjKDE6MTApLF0KYm9va19yYXRpbmdzX2NvdW50X3RvcDEwCmdncGxvdChkYXRhPWJvb2tfcmF0aW5nc19jb3VudF90b3AxMCwgYWVzKHg9cmVvcmRlcih0aXRsZSwgcmF0aW5nc19jb3VudCkseT1yYXRpbmdzX2NvdW50LCBmaWxsID0gZmFjdG9yKGMoMTpucm93KGJvb2tfcmF0aW5nc19jb3VudF90b3AxMCkpKSkpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgY29vcmRfZmxpcCgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJQYWlyZWQiKSArIAogIGdndGl0bGUoJ0Jvb2tzL3NlcmllcyB3aXRoIGhpZ2hlc3QgbnVtYmVyIG9mIHJhdGluZ3MgY291bnQnKSArIAogIGxhYnMoeCA9ICJCb29rL3NlcmllcyIsIHkgPSAiTnVtYmVyIG9mIHJhdGluZ3MiKSArCiAgZ2VvbV90ZXh0KAogICAgYWVzKGxhYmVsID0gcmF0aW5nc19jb3VudCksIGNvbG91ciA9ICJibGFjayIsCiAgICBoanVzdCA9IC0wLjEsIHNpemUgPSAyLAogICAgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDEpLAogICAgaW5oZXJpdC5hZXMgPSBUUlVFCiAgKQoKYXV0aG9yX21vc3RfYm9va19yYXRpbmdzX2NvdW50IDwtIGRhdGEuZnJhbWUodGFibGUoYm9va19yYXRpbmdzX2NvdW50X3RvcDEwJGF1dGhvcnMpKQpuYW1lcyhhdXRob3JfbW9zdF9ib29rX3JhdGluZ3NfY291bnQpIDwtIGMoJ2F1dGhvcicsICdudW1iZXJfb2ZfYm9va3MnKQphdXRob3JfbW9zdF9ib29rX3JhdGluZ3NfY291bnQgPC0gYXV0aG9yX21vc3RfYm9va19yYXRpbmdzX2NvdW50W29yZGVyKC1hdXRob3JfbW9zdF9ib29rX3JhdGluZ3NfY291bnQkbnVtYmVyX29mX2Jvb2tzKSxdCmF1dGhvcl9tb3N0X2Jvb2tfcmF0aW5nc19jb3VudApgYGAKClRoZXJlIGlzIGEgaHVnZSBnYXAgYmV0d2VlbiB0aGUgZmlyc3QgdHdvIG1vc3QgcmF0ZWQgYm9va3MgYW5kIHRoZSByZW1haW5pbmdzLiBTcGVjaWZpY2FsbHkgc3BlYWtpbmcsIHRoZXJlIGlzIGEgMiBtaWxsaW9uIHJhdGluZyBnYXAgYmV0d2VlbiBUd2lsaWdodCAjMSAtIHRoZSBzZWNvbmQgbW9zdCByYXRlZCBib29rIGFuZCAiVGhlIEhvYmJpdCBvciBUaHJlZSBhbmQgQmFjayBBZ2FpbiIgLSB0aGUgdGhpcmQgbW9zdCByYXRlZCBib29rLiBFdmVuIGJldHdlZW4gdGhlIGZpcnN0IHBvc2l0aW9uLCAiSGFycnkgUG90dGVyIGFuZCB0aGUgU29yY2VyZXIncyBTdG9uZSIgYW5kIHRoZSBzZWNvbmQgb25lLCB0aGVyZSBpcyBhbHNvIGEgc2lnbmlmaWNhbnQgZ2FwIG9mIGFib3V0IDEuMyBtaWxsaW9uIHJhdGluZ3MuIEhvd2V2ZXIsIGJldHdlZW4gdGhlIHRoaXJkIGFuZCB0aGUgMTB0aCBwb3NpdGlvbiwgdGhlIGdhcCBpcyBqdXN0IGFib3V0IDM1MC4wMDAgcmF0aW5ncy4KCkFib3V0IHRoZSBhdXRob3JzJyBsaXN0IG9mIHRvcCAxMCBib29rcywgd2UgY2FuIGVhc2lseSBzZWUgdGhhdCBKLksgUm93bGluZyBhbmQgaGVyIEhhcnJ5IFBvdHRlciBzZXJpZXMgYXJlIGRvbWluYXRpbmcgdGhlIHJhdGluZ3MgY291bnQsIHdpdGggNCBvZiBoZXIgYm9va3MgaW4gdGhlIHNlcmllcyBtYWtpbmcgdGhlaXIgd2F5cyB0byB0aGUgdG9wIDEwLiAKICAtIEhhcnJ5IFBvdHRlciBhbmQgdGhlIFNvcmNlcmVyJ3MgU3RvbmUgKEhhcnJ5IFBvdHRlciAjMSkgYXQgZmlyc3QgcGxhY2UKICAtIEhhcnJ5IFBvdHRlciBhbmQgdGhlIFByaXNvbmVyIG9mIEF6a2FiYW4gKEhhcnJ5IFBvdHRlciAjMykgYXQgc2l4dGggcGxhY2UKICAtIEhhcnJ5IFBvdHRlciBhbmQgdGhlIENoYW1iZXIgb2YgU2VjcmV0cyAoSGFycnkgUG90dGVyICMyKSBhdCBzZXZlbnRoIHBsYWNlCiAgLSBIYXJyeSBQb3R0ZXIgYW5kIHRoZSBPcmRlciBvZiB0aGUgUGhvZW5peCAoSGFycnkgUG90dGVyICM1KSBhdCB0ZW50aCBwbGFjZQogIApMYXJnZSBudW1iZXIgb2YgcmF0aW5ncyBjb3VudCwgYWxvbmdzaWRlIHdpdGggaGlnaCBhdmVyYWdlIHJhdGluZyAoYWxsIG92ZXIgNC40KSwgdGhlc2UgZGVmaW5pdGVseSBzaG93cyBob3cgc3VjY2Vzc2Z1bCB0aGlzIHNlcmllcyBoYXZlIGJlZW4uCgojIzEwIEJvb2tzIHdpdGggdGhlIGhpZ2hlc3QgbnVtYmVyIG9mIHJhdGluZ3MgY291bnQKCmBgYHtyfQpib29rX3RleHRfcmV2aWV3c19jb3VudCA8LSBnb29kcmVhZHNbb3JkZXIoLWdvb2RyZWFkcyR0ZXh0X3Jldmlld3NfY291bnQpLF0KCmJvb2tfdGV4dF9yZXZpZXdzX2NvdW50X3RvcDEwIDwtIGJvb2tfdGV4dF9yZXZpZXdzX2NvdW50W2MoMToxMCksXQpib29rX3RleHRfcmV2aWV3c19jb3VudF90b3AxMApnZ3Bsb3QoZGF0YT1ib29rX3RleHRfcmV2aWV3c19jb3VudF90b3AxMCwgYWVzKHg9cmVvcmRlcih0aXRsZSwgdGV4dF9yZXZpZXdzX2NvdW50KSx5PXRleHRfcmV2aWV3c19jb3VudCwgZmlsbCA9IGZhY3RvcihjKDE6bnJvdyhib29rX3RleHRfcmV2aWV3c19jb3VudF90b3AxMCkpKSkpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgY29vcmRfZmxpcCgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJQYWlyZWQiKSArIAogIGdndGl0bGUoJ0Jvb2tzL3NlcmllcyB3aXRoIGhpZ2hlc3QgbnVtYmVyIG9mIHRleHQgcmV2aWV3cycpICsgCiAgbGFicyh4ID0gIkJvb2svc2VyaWVzIiwgeSA9ICJOdW1iZXIgb2YgdGV4dCByZXZpZXdzIikgKwogIGdlb21fdGV4dCgKICAgIGFlcyhsYWJlbCA9IHRleHRfcmV2aWV3c19jb3VudCksIGNvbG91ciA9ICJibGFjayIsCiAgICBoanVzdCA9IC0wLjEsIHNpemUgPSAyLAogICAgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDEpLAogICAgaW5oZXJpdC5hZXMgPSBUUlVFCiAgKQoKYXV0aG9yX21vc3RfYm9va190ZXh0X3Jldmlld3NfY291bnQgPC0gZGF0YS5mcmFtZSh0YWJsZShib29rX3RleHRfcmV2aWV3c19jb3VudF90b3AxMCRhdXRob3JzKSkKbmFtZXMoYXV0aG9yX21vc3RfYm9va190ZXh0X3Jldmlld3NfY291bnQpIDwtIGMoJ2F1dGhvcicsICdudW1iZXJfb2ZfYm9va3MnKQphdXRob3JfbW9zdF9ib29rX3RleHRfcmV2aWV3c19jb3VudCA8LSBhdXRob3JfbW9zdF9ib29rX3RleHRfcmV2aWV3c19jb3VudFtvcmRlcigtYXV0aG9yX21vc3RfYm9va190ZXh0X3Jldmlld3NfY291bnQkbnVtYmVyX29mX2Jvb2tzKSxdCmF1dGhvcl9tb3N0X2Jvb2tfdGV4dF9yZXZpZXdzX2NvdW50CmBgYAoKT25jZSBhZ2FpbiwgdGhlcmUgaXMgYSBzaWduaWZpY2FudCBnYXAgYmV0d2VlbiB0aGUgZmlyc3QgZm91ciBwb3NpdGlvbnM7IGFmdGVyIHRoYXQsIHRoZSBnYXAgaXMgZ2V0dGluZyBtdWNoIG5hcnJvd2VyLiAKCk1vcmVvdmVyLCB0aGlzIHRpbWUsIHRoZSBhdXRob3IncyBsaXN0IG9mIHRoZXNlIGJvb2tzIGFyZSBtb3JlIHdpZGVseSBzcHJlYWQsIHdpdGggMTAgZGlmZmVyZW50IGF1dGhvcnMsIGVhY2ggaGF2aW5nIG9uZSBvZiB0aGVpciBib29rcyBpbiB0aGUgdG9wIDEwIGxpc3QuCgpXZSBoYXZlIHNlZW4gc29tZSBmYW1pbGlhciBuYW1lcyB0aGF0IGFwcGVhciBpbiBib3RoIHRvcCAxMCBvZiByYXRpbmdzIGNvdW50IGFuZCB0ZXh0IHJldmlld3MgY291bnQuIFRoZSBxdWVzdGlvbiBpcywgaG93IG1hbnkgb2YgdGhlbSBhcmUgdGhlcmUsIGFuZCB3aGF0IGlzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlaXIgcGVyZm9ybWFuY2UgaW4gdGhlc2UgdHdvIGNyaXRlcmlhPwoKIyNDb21tb24gYm9va3Mgb2YgdGhlIHR3byB0b3AgMTAgb2YgcmF0aW5ncyBjb3VudCBhbmQgdGV4dCByZXZpZXdzIGNvdW50CgpgYGB7cn0KY29tbW9uX3RvcDEwIDwtIGludGVyc2VjdChib29rX3RleHRfcmV2aWV3c19jb3VudF90b3AxMCwgYm9va19yYXRpbmdzX2NvdW50X3RvcDEwKSAgCmNvbW1vbl90b3AxMApgYGAKCkFsdGhvdWdoIHRoZXJlIGlzIGEgdmVyeSBzdHJvbmcgcG9zaXRpdmUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgdHdvIHZhcmlhYmxlcywgdGhlcmUgYXJlIG9ubHkgdHdvIGJvb2tzIHRoYXQgYmVsb25nIHRvIGJvdGggdG9wIDEwIG9mIHRoZXNlIHR3byBjcml0ZXJpYS4gVGhleSBhcmU6CgogICogVHdpbGlnaHQgKFR3aWxpZ2h0ICMxKQogICogSGFycnkgUG90dGVyIGFuZCB0aGUgU29yY2VyZXIncyBTdG9uZSAoSGFycnkgUG90dGVyICMxKQoKVGhlc2UgdHdvIGJvb2tzIGFyZSBwZXJmb3JtaW5nIGV4dHJlbWVseSB3ZWxsIGluIGJvdGggY3JpdGVyaWEsIGFuZCBzbyBmYXIgdGhlIGxlYWRpbmcgYm9va3MgaW4gdGhpcyBkYXRhc2V0LCBjb25zaWRlcmluZyB0aGUgbnVtYmVyIG9mIHJhdGluZ3MvcmV2aWV3czoKCiAgKiBXaXRoIFR3aWxpZ2h0IChUd2lsaWdodCAjMSk6IFRvcCAyIGluIHJhdGluZ3MgY291bnQsIFRvcCAxIGluIHRleHQgcmV2aWV3cyBjb3VudAogICogV2l0aCBIYXJyeSBQb3R0ZXIgYW5kIHRoZSBTb3JjZXJlcidzIFN0b25lIChIYXJyeSBQb3R0ZXIgIzEpOiBUb3AgMSBpbiByYXRpbmdzIGNvdW50LCBUb3AgMyBpbiB0ZXh0IHJldmlld3MgY291bnQuCgoKYGBge3J9CmF1dGhvcl9tb3N0X2Jvb2tzIDwtIGRhdGEuZnJhbWUodGFibGUoZ29vZHJlYWRzJGF1dGhvcnMpLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCm5hbWVzKGF1dGhvcl9tb3N0X2Jvb2tzKSA8LSBjKCdhdXRob3InLCd0b3RhbF9udW1iZXJfb2ZfYm9va3MnKQphdXRob3JfbW9zdF9ib29rcyA8LSBhdXRob3JfbW9zdF9ib29rc1tvcmRlcigtYXV0aG9yX21vc3RfYm9va3MkdG90YWxfbnVtYmVyX29mX2Jvb2tzKSxdIApoZWFkKGF1dGhvcl9tb3N0X2Jvb2tzKQoKYXV0aG9yX21vc3RfYm9va3NfdG9wMTAgPC0gYXV0aG9yX21vc3RfYm9va3NbYygxOjEwKSxdCmdncGxvdChkYXRhPWF1dGhvcl9tb3N0X2Jvb2tzX3RvcDEwLCBhZXMoeD1yZW9yZGVyKGF1dGhvciwgdG90YWxfbnVtYmVyX29mX2Jvb2tzKSx5PXRvdGFsX251bWJlcl9vZl9ib29rcywgZmlsbCA9IGZhY3RvcihjKDE6bnJvdyhhdXRob3JfbW9zdF9ib29rc190b3AxMCkpKSkpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgY29vcmRfZmxpcCgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJTZXQzIikgKyAKICBnZ3RpdGxlKCdBdXRob3Igd2l0aCBncmVhdGVzdCBudW1iZXIgb2YgYm9va3MgYXBwZWFyZWQnKSArIAogIGxhYnMoeCA9ICJBdXRob3IiLCB5ID0gIlRvdGFsIG51bWJlciBvZiBib29rcyIpICsKICBnZW9tX3RleHQoCiAgICBhZXMobGFiZWwgPSB0b3RhbF9udW1iZXJfb2ZfYm9va3MpLCBjb2xvdXIgPSAiYmxhY2siLAogICAgaGp1c3QgPSAtMC41LCBzaXplID0gMywKICAgIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAxKSwKICAgIGluaGVyaXQuYWVzID0gVFJVRQogICkKCmF1dGhvcl9tb3N0X2Jvb2tzX3RvcDEwCgpgYGAKCk9uY2UgYWdhaW4sIHRoZSBmaXJzdCB0d28gYXV0aG9ycyBjcmVhdGVzIGEgaHVnZSBnYXAgd2l0aCB0aGUgcmVtYWluaW5nLiBCb3RoIEFnYXRoYSBDaHJpc3RpZSBhbmQgU3RlcGhlbiBLaW5nIGhhdmUgbW9yZSB0aGFuIDY1IGJvb2tzLCB3aGljaCBpcyBhdCBsZWFzdCAxOCBib29rcyBtb3JlIHRoYW4gdGhlIHRoaXJkIGF1dGhvciwgT3Jzb24gU2NvdHQgQ2FyZC4gCgojI1doYXQgYXJlIHRoZSBhdXRob3JzIHdpdGggdGhlIGdyZWF0ZXN0IG51bWJlciBvZiBoaWdobHktcmFua2VkIGJvb2tzPwoKVGhlIGRlZmluaXRpb24gb2YgImhpZ2hseS1yYW5rZWQgYm9va3MiIGluIHRoaXMgY29udGV4dCBpcyByZWxhdGl2ZS4gQXMgc3VjaCwgd2UgY29uc2lkZXIgc29tZSBvZiB0aGUgZm9sbG93aW5nIGNhc2VzLiBXZSBkZWZpbmUgYSBib29rIHRvIGJlIGhpZ2hseS1yYW5rZWQgaWYgaXRzIGF2ZXJhZ2UgcmFua2luZyBpcyBncmVhdGVyIHRoYW46CgogICogNC4wCiAgKiA0LjMKICAqIDQuNQogIApgYGB7cn0KcHJpbnQocGFzdGUoJ1RoZXJlIGFyZScsc3VtKGdvb2RyZWFkcyRhdmVyYWdlX3JhdGluZyA+PSA0LjApLCdib29rcyB0aGF0IGhhdmUgYSByYXRpbmcgb2YgZ3JlYXRlciB0aGFuIG9yIGVxdWFsIHRvIDQuMCwgd2hpY2ggYWNjb3VudHMgZm9yJyxyb3VuZChzdW0oZ29vZHJlYWRzJGF2ZXJhZ2VfcmF0aW5nID49IDQuMCkvbnJvdyhnb29kcmVhZHMpKjEwMCwgZGlnaXRzID0gMiksJ3BlcmNlbnQgb2YgdGhlIGJvb2tzIGluIHRoaXMgZGF0YXNldC4nKSkKCnByaW50KHBhc3RlKCdUaGVyZSBhcmUnLHN1bShnb29kcmVhZHMkYXZlcmFnZV9yYXRpbmcgPj0gNC4zKSwnYm9va3MgdGhhdCBoYXZlIGEgcmF0aW5nIG9mIGdyZWF0ZXIgdGhhbiBvciBlcXVhbCB0byA0LjMsIHdoaWNoIGFjY291bnRzIGZvcicscm91bmQoc3VtKGdvb2RyZWFkcyRhdmVyYWdlX3JhdGluZyA+PSA0LjMpL25yb3coZ29vZHJlYWRzKSoxMDAsIGRpZ2l0cyA9IDIpLCdwZXJjZW50IG9mIHRoZSBib29rcyBpbiB0aGlzIGRhdGFzZXQuJykpCgpwcmludChwYXN0ZSgnVGhlcmUgYXJlJyxzdW0oZ29vZHJlYWRzJGF2ZXJhZ2VfcmF0aW5nID49IDQuNSksJ2Jvb2tzIHRoYXQgaGF2ZSBhIHJhdGluZyBvZiBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gNC41LCB3aGljaCBhY2NvdW50cyBmb3InLHJvdW5kKHN1bShnb29kcmVhZHMkYXZlcmFnZV9yYXRpbmcgPj0gNC41KS9ucm93KGdvb2RyZWFkcykqMTAwLCBkaWdpdHMgPSAyKSwncGVyY2VudCBvZiB0aGUgYm9va3MgaW4gdGhpcyBkYXRhc2V0LicpKQpgYGAKCgpgYGB7cn0KYXV0aG9yX21vc3RfYm9va19oaWdoX3JhdGluZ3MgPC0gZ29vZHJlYWRzW2dvb2RyZWFkcyRhdmVyYWdlX3JhdGluZyA+PSA0LjAsXQphdXRob3JfbW9zdF9ib29rc19oaWdoX3JhdGluZ3MgPC0gZGF0YS5mcmFtZSh0YWJsZShhdXRob3JfbW9zdF9ib29rX2hpZ2hfcmF0aW5ncyRhdXRob3JzKSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpuYW1lcyhhdXRob3JfbW9zdF9ib29rc19oaWdoX3JhdGluZ3MpIDwtIGMoJ2F1dGhvcicsJ251bWJlcl9vZl9ib29rc18+PV80LjAnKQphdXRob3JfbW9zdF9ib29rc19oaWdoX3JhdGluZ3MgPC0gYXV0aG9yX21vc3RfYm9va3NfaGlnaF9yYXRpbmdzW29yZGVyKC1hdXRob3JfbW9zdF9ib29rc19oaWdoX3JhdGluZ3MkYG51bWJlcl9vZl9ib29rc18+PV80LjBgKSxdIAoKYXV0aG9yX21vc3RfYm9va19oaWdoX3JhdGluZ3NfMiA8LSBnb29kcmVhZHNbZ29vZHJlYWRzJGF2ZXJhZ2VfcmF0aW5nID49IDQuMyxdCmF1dGhvcl9tb3N0X2Jvb2tzX2hpZ2hfcmF0aW5nc18yIDwtIGRhdGEuZnJhbWUodGFibGUoYXV0aG9yX21vc3RfYm9va19oaWdoX3JhdGluZ3NfMiRhdXRob3JzKSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpuYW1lcyhhdXRob3JfbW9zdF9ib29rc19oaWdoX3JhdGluZ3NfMikgPC0gYygnYXV0aG9yJywnbnVtYmVyX29mX2Jvb2tzXz49XzQuMycpCmF1dGhvcl9tb3N0X2Jvb2tzX2hpZ2hfcmF0aW5nc18yIDwtIGF1dGhvcl9tb3N0X2Jvb2tzX2hpZ2hfcmF0aW5nc18yW29yZGVyKC1hdXRob3JfbW9zdF9ib29rc19oaWdoX3JhdGluZ3NfMiRgbnVtYmVyX29mX2Jvb2tzXz49XzQuM2ApLF0gCgphdXRob3JfbW9zdF9ib29rX2hpZ2hfcmF0aW5nc18zIDwtIGdvb2RyZWFkc1tnb29kcmVhZHMkYXZlcmFnZV9yYXRpbmcgPj0gNC41LF0KYXV0aG9yX21vc3RfYm9va3NfaGlnaF9yYXRpbmdzXzMgPC0gZGF0YS5mcmFtZSh0YWJsZShhdXRob3JfbW9zdF9ib29rX2hpZ2hfcmF0aW5nc18zJGF1dGhvcnMpLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCm5hbWVzKGF1dGhvcl9tb3N0X2Jvb2tzX2hpZ2hfcmF0aW5nc18zKSA8LSBjKCdhdXRob3InLCdudW1iZXJfb2ZfYm9va3NfPj1fNC41JykKYXV0aG9yX21vc3RfYm9va3NfaGlnaF9yYXRpbmdzXzMgPC0gYXV0aG9yX21vc3RfYm9va3NfaGlnaF9yYXRpbmdzXzNbb3JkZXIoLWF1dGhvcl9tb3N0X2Jvb2tzX2hpZ2hfcmF0aW5nc18zJGBudW1iZXJfb2ZfYm9va3NfPj1fNC41YCksXSAKCmF1dGhvcl9tb3N0X2Jvb2tzX2hpZ2hfcmF0aW5nc190b3AxMCA8LSBhdXRob3JfbW9zdF9ib29rc19oaWdoX3JhdGluZ3NbYygxOjEwKSxdCmdncGxvdChkYXRhPWF1dGhvcl9tb3N0X2Jvb2tzX2hpZ2hfcmF0aW5nc190b3AxMCwgYWVzKHg9cmVvcmRlcihhdXRob3IsIGBudW1iZXJfb2ZfYm9va3NfPj1fNC4wYCkseT1gbnVtYmVyX29mX2Jvb2tzXz49XzQuMGAsIGZpbGwgPSBmYWN0b3IoYygxOm5yb3coYXV0aG9yX21vc3RfYm9va3NfaGlnaF9yYXRpbmdzX3RvcDEwKSkpKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKyAKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICBjb29yZF9mbGlwKCkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IlNldDMiKSArIAogIGdndGl0bGUoJ0F1dGhvciB3aXRoIGdyZWF0ZXN0IG51bWJlciBvZiBib29rcyByYW5rZWQgPj0gNC4wJykgKyAKICBsYWJzKHggPSAiQXV0aG9yIiwgeSA9ICJUb3RhbCBudW1iZXIgb2YgaGlnaGx5IGJvb2tzIHJhbmtlZCA+PSA0LjAiKSArCiAgZ2VvbV90ZXh0KAogICAgYWVzKGxhYmVsID0gYG51bWJlcl9vZl9ib29rc18+PV80LjBgKSwgY29sb3VyID0gImJsYWNrIiwKICAgIGhqdXN0ID0gLTAuNSwgc2l6ZSA9IDMsCiAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gMSksCiAgICBpbmhlcml0LmFlcyA9IFRSVUUKICApCgphdXRob3JfbW9zdF9ib29rc19oaWdoX3JhdGluZ3NfdG9wMTBfMiA8LSBhdXRob3JfbW9zdF9ib29rc19oaWdoX3JhdGluZ3NfMltjKDE6MTApLF0KZ2dwbG90KGRhdGE9YXV0aG9yX21vc3RfYm9va3NfaGlnaF9yYXRpbmdzX3RvcDEwXzIsIGFlcyh4PXJlb3JkZXIoYXV0aG9yLCBgbnVtYmVyX29mX2Jvb2tzXz49XzQuM2ApLHk9YG51bWJlcl9vZl9ib29rc18+PV80LjNgLCBmaWxsID0gZmFjdG9yKGMoMTpucm93KGF1dGhvcl9tb3N0X2Jvb2tzX2hpZ2hfcmF0aW5nc190b3AxMF8yKSkpKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKyAKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICBjb29yZF9mbGlwKCkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IlNldDMiKSArIAogIGdndGl0bGUoJ0F1dGhvciB3aXRoIGdyZWF0ZXN0IG51bWJlciBvZiBib29rcyByYW5rZWQgPj0gNC4zJykgKyAKICBsYWJzKHggPSAiQXV0aG9yIiwgeSA9ICJUb3RhbCBudW1iZXIgb2YgYm9va3MgcmFua2VkID49IDQuMyIpICsKICBnZW9tX3RleHQoCiAgICBhZXMobGFiZWwgPSBgbnVtYmVyX29mX2Jvb2tzXz49XzQuM2ApLCBjb2xvdXIgPSAiYmxhY2siLAogICAgaGp1c3QgPSAtMC41LCBzaXplID0gMywKICAgIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAxKSwKICAgIGluaGVyaXQuYWVzID0gVFJVRQogICkKCmF1dGhvcl9tb3N0X2Jvb2tzX2hpZ2hfcmF0aW5nc190b3AxMF8zIDwtIGF1dGhvcl9tb3N0X2Jvb2tzX2hpZ2hfcmF0aW5nc18zW2MoMToxMCksXQpnZ3Bsb3QoZGF0YT1hdXRob3JfbW9zdF9ib29rc19oaWdoX3JhdGluZ3NfdG9wMTBfMywgYWVzKHg9cmVvcmRlcihhdXRob3IsIGBudW1iZXJfb2ZfYm9va3NfPj1fNC41YCkseT1gbnVtYmVyX29mX2Jvb2tzXz49XzQuNWAsIGZpbGwgPSBmYWN0b3IoYygxOm5yb3coYXV0aG9yX21vc3RfYm9va3NfaGlnaF9yYXRpbmdzX3RvcDEwXzMpKSkpKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArIAogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKwogIGNvb3JkX2ZsaXAoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZT0iU2V0MyIpICsgCiAgZ2d0aXRsZSgnQXV0aG9yIHdpdGggZ3JlYXRlc3QgbnVtYmVyIG9mIGJvb2tzIHJhbmtlZCA+PSA0LjUnKSArIAogIGxhYnMoeCA9ICJBdXRob3IiLCB5ID0gIlRvdGFsIG51bWJlciBvZiBib29rcyByYW5rZWQgPj0gNC41IikgKwogIGdlb21fdGV4dCgKICAgIGFlcyhsYWJlbCA9IGBudW1iZXJfb2ZfYm9va3NfPj1fNC41YCksIGNvbG91ciA9ICJibGFjayIsCiAgICBoanVzdCA9IC0wLjUsIHNpemUgPSAzLAogICAgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDEpLAogICAgaW5oZXJpdC5hZXMgPSBUUlVFCiAgKQoKbGlzdF9hdXRob3JzX3RvcDEwcyA8LSBSZWR1Y2UoZnVuY3Rpb24oeCx5KSBtZXJnZSh4LHksYnk9ImF1dGhvciIsYWxsPVRSVUUpICxsaXN0KGF1dGhvcl9tb3N0X2Jvb2tzX2hpZ2hfcmF0aW5nc190b3AxMCxhdXRob3JfbW9zdF9ib29rc19oaWdoX3JhdGluZ3NfdG9wMTBfMixhdXRob3JfbW9zdF9ib29rc19oaWdoX3JhdGluZ3NfdG9wMTBfMykpCgpsaXN0X2F1dGhvcnNfdG9wMTBzCmBgYAoKRnJvbSB0aGUgaW5mb3JtYXRpb24gZ2F0aGVyZWQgYWJvdmUsIHdlIGNhbiBzdWJqZWN0aXZlbHkgZGVmaW5lIHRoZSBxdWFsaXR5IG9mIGEgYm9vayBiYXNlZCBvbiBpdHMgcmF0aW5nIGFzIGZvbGxvdzoKCiAgKiBCZXR3ZWVuIDQuMCBhbmQgNC4zOiBBdmVyYWdlL0ZhaXIgCiAgKiBCZXR3ZWVuIDQuMyBhbmQgNC41OiBHb29kIAogICogQWJvdmUgNC41OiBFeGNlbGxlbnQKICAKVGhlcmUgYXJlIGEgdG90YWwgb2YgMjEgYXV0aG9ycyB0aGF0IGFwcGVhciBpbiB0aGUgdGhyZWUgdG9wIDEwIG9mIGF1dGhvcnMgd2hvIGhhcyB0aGUgZ3JlYXRlc3QgbnVtYmVyIG9mIGJvb2tzICh3aXRoIDMgZGlmZmVyZW50IGN1dG9mZnMpLiBGcm9tIHRoZSB0YWJsZSBhYm92ZSwgd2UgaGF2ZSB0aGUgZm9sbG93aW5nIG9ic2VydmF0aW9uczoKCiAgKiBUaGVyZSBhcmUgc29tZSBhdXRob3JzIHdobyB3cml0ZSBhIGxvdCBvZiBhYm92ZSBhdmVyYWdlL2ZhaXIgYm9va3MsIGJ1dCBvbmx5IHNvbWUgb3Igbm9uZSBvZiB0aGVtIGFyZSBnb29kIG9yIGFib3ZlLiBUaGlzIG1lYW5zIHRoYXQgbW9zdCBvZiB0aGVpciBib29rcyBhcmUgcmFua2VkIGJldHdlZW4gNC4wIGFuZCA0LjMuIFNvbWUgYXV0aG9ycyBpbiB0aGlzIGdyb3VwIGFyZSBKYW5ldCBFdmFub3ZpY2gsIE1lcmNlZGVzIExhY2tleSwgU3RlcGhlbiBLaW5nLCBSdW1pa28gVGFrYWhhc2hpLCBUZXJyeSBQcmF0Y2hldHQsIEFnYXRoYSBDaHJpc3RpZS4gVGhleSBhcmUgaW4gdG9wIDEwIHdpdGggdGhlIDQuMCBjdXRvZmYsIGJ1dCBjYW4gbm90IG1ha2UgaXQgdG8gdG9wIDEwIHdpdGggaGlnaGVyIGN1dG9mZnMuCiAgICAgICsgRm9yIGV4YW1wbGUsIFJ1bWlrbyBUYWthaGFzaGkgaGFzIDQ0IGJvb2tzIHJhbmtlZCA0LjAgb3IgYWJvdmUsIGJ1dCBoZSBoYXMgZmV3ZXIgYm9va3MgcmFua2VkIG92ZXIgNC4zIHRoYW4gSGlyb211IEFyYWthd2EgKDggYm9va3MpLgogICogT24gdGhlIG90aGVyIGhhbmQsIHRoZXJlIGFyZSBzb21lIGF1dGhvcnMgd2hvIHdyaXRlIGZld2VyIGJvb2tzLCBidXQgbW9zdCBvZiB0aGVpciBib29rcyBhcmUgcmFua2VkIGZhaXJseSBoaWdoLiBUaGUgdHdvIG1vc3Qgbm9ibGUgbmFtZXMgZm9yIHRoaXMgZ3JvdXAgYXJlIGlsbCBXYXR0ZXJzb24gYW5kIEouSy5Sb3dsaW5nLiAKICAgICAgKyBCaWxsIFdhdHRlcnNvbiBhcmUgbm90IGluIHRvcCAxMCB3aXRoIGJvb2tzIG92ZXIgNC4wLCBidXQgaGUgaGFzIDE2IGJvb2tzIHdpdGggcmF0aW5ncyBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gNC4zLCAxNSBvZiB3aGljaCBoYXMgcmF0aW5ncyBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gNC41CiAgICAgICsgSi5LLiBSb3dsaW5nIGhhcyAzMSBib29rcyBhYm92ZSBhdmVyYWdlLCBhbGwgb2YgdGhlbSBoYXZpbmcgcmF0aW5ncyBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gNC4zLCBhbmQgMTAgb2YgdGhlbSBoYXMgcmF0aW5ncyBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gNC41CiAgICAgICsgSGlyb211IEFyYWthd2EgLSBBa2lyYSBXYXRhbmFiZSBoYXMgb25seSAxMiBib29rcyB3aG9zZSByYXRpbmdzIGFyZSBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gNC4zLCBidXQgMTEgb2YgdGhlbSBoYXZlIHJhdGluZ3MgZ3JlYXRlciB0aGFuIG9yIGVxdWFsIHRvIDQuNQogICogVGhlcmUgYXJlIHNvbWUgYXV0aG9ycyB3aG8gd3JpdGUgdmVyeSBmZXcgYm9va3MgKGkuZSBkb2VzIG5vdCBhcHBlYXIgaW4gdG9wIDEwIG9mIGN1dG9mZnMgNC4wIGFuZCA0LjMpLCBidXQgbW9zdC9hbGwgb2YgdGhlaXIgYm9va3MgYXJlIHZlcnkgaGlnaGx5IHJhdGVkIChncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gNC41KS4KICAgICsgSGF5YW8gTWl5YXpha2kgd2l0aCA2IGJvb2tzIHJhdGVkIGdyZWF0ZXIgdGhhbiBvciBlcXVhbCB0byA0LjUKICAgICsgSGlyb211IEFyYWthd2EgYW5kIEphbmUgQXVzdGVuIHdpdGggNCBib29rcyByYXRlZCBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gNC41CiAgICAKV2UgY2FuIGFsc28gc2VlIHRoYXQsIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIDQuMCBhbmQgNC4zIGlzIG9ubHkgMC4zLCBidXQgdGhlIGFtb3VudCBvZiBib29rcyByYXRlZCBoYXMgZGVjcmVhc2VkIDUgdGltZXMuIAoKI0FwcGx5aW5nIEstbWVhbnMgYWxnb3JpdGhtIHRvIGNsdXN0ZXIgZGF0YXNldHMKCkstbWVhbnMgY2x1c3RlcmluZyBpcyB0aGUgbW9zdCBjb21tb25seSB1c2VkIHVuc3VwZXJ2aXNlZCBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobSBmb3IgcGFydGl0aW9uaW5nIGEgZ2l2ZW4gZGF0YSBzZXQgaW50byBhIHNldCBvZiBrIGdyb3VwcyAoaS5lLiBrIGNsdXN0ZXJzKSwgd2hlcmUgayByZXByZXNlbnRzIHRoZSBudW1iZXIgb2YgZ3JvdXBzIHByZS1zcGVjaWZpZWQgYnkgdGhlIGFuYWx5c3QuCgpGaXJzdCwgd2Ugd2lsbCBidWlsZCBhIG1vZGVsIHRvIGNsdXN0ZXIgdGhpcyBkYXRhc2V0IGJhc2VkIG9uIHR3byB2YXJpYWJsZXM6IGF2ZXJhZ2VfcmF0aW5nIGFuZCByYXRpbmdzX2NvdW50LgoKVG8gZGV0ZXJtaW5lIHRoZSBvcHRpbWFsIG51bWJlciBvIGNsdXN0ZXJzLCB3ZSB1c2UgdGhlIHR3byBmb2xsb3dpbmcgcG9wdWxhciBtZXRob2RzOgoKICAqIEVsYm93IG1ldGhvZAogICogU2lob3VldHRlIG1ldGhvZAogIApgYGB7cn0KZ29vZHJlYWRzX2ttZWFuczEgPC0gZ29vZHJlYWRzWyxjKCdhdmVyYWdlX3JhdGluZycsJ3JhdGluZ3NfY291bnQnKV0KCnNldC5zZWVkKDEyMykKCmZ2aXpfbmJjbHVzdChnb29kcmVhZHNfa21lYW5zMSwga21lYW5zLCBtZXRob2QgPSAid3NzIikKCmZ2aXpfbmJjbHVzdChnb29kcmVhZHNfa21lYW5zMSwga21lYW5zLCBtZXRob2QgPSAic2lsaG91ZXR0ZSIpCmBgYAoKRnJvbSBib3RoIG9mIHRoZSB0d28gYXBwcm9hY2hlcyBhYm92ZSwgdGhlIG9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzIHNlZW0gdG8gYmUgYXQgNS4gQXMgYSByZXN1bHQsIHdlIHdpbGwgcGVyZm9ybSBhbiBhbmFseXNpcyBhbmQgZXh0cmFjdCB0aGUgcmVzdWx0cyB3aXRoIDUgY2x1c3RlcnMuCgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQpmaW5hbF9rbWVhbnMxIDwtIGttZWFucyhnb29kcmVhZHNfa21lYW5zMSwgNSwgbnN0YXJ0ID0gMjUpCnByaW50KGZpbmFsX2ttZWFuczEpCnN0cihmaW5hbF9rbWVhbnMxKQoKZnZpel9jbHVzdGVyKGZpbmFsX2ttZWFuczEsIGRhdGEgPSBnb29kcmVhZHNfa21lYW5zMSkKYGBgCgpXZSBjYW4gc2VlIHRoYXQgdGhlIGRhdGEgYXJlIGNsZWFybHkgY2xhc3NpZmllZCBpbnRvIDUgZGlmZmVyZW50IGNsdXN0ZXJzLiBUaGVzZSBjbHVzdGVycyBjYW4gYmUgb3JkZXJlZCBieSByYXRpbmdzIGNvdW50IChpLmUsIHRoZSBtZWFuIHJhdGluZ3MgY291bnQgb2YgdGhlc2UgY2x1c3RlcnMgY2FuIGJlIG9yZGVyZWQgaW4gYSBpbmNyZWFzaW5nIG9yZGVyIHdpdGggY2xlYXIgZGlmZmVyZW5jZXMpLgoKQXMgdGhlIHJhdGluZyBjb3VudCBkZWNyZWFzZXMsIHRoZSBhdmVyYWdlIHJhdGluZyBhcmUgbW9yZSB3aWRlbHkgc3ByZWFkIChpLmUgdGhlIGF2ZXJhZ2UgcmF0aW5nIHJhdGUgaXMgaGlnaGVyKSwgd2hpY2ggbWVhbnMgdGhhdCB0aGUgYXZlcmFnZSByYXRpbmcgaXMgbmVpdGhlciBjb25jZW50cmF0ZWQgbm9yIGNvcnJlY3QuCgpXZSBhbHNvIGNhbiBleHRyYWN0IHRoZSBjbHVzdGVycyBhbmQgYWRkIHRvIG91ciBpbml0aWFsIGRhdGEgZm9yIGZ1dGhlciBhbmFseXNpcyBhdCB0aGUgY2x1c3RlciBsZXZlbDoKCmBgYHtyfQpnb29kcmVhZHNfa21lYW5zMV9yZXN1bHQgPC0gZ29vZHJlYWRzICU+JQogICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKENsdXN0ZXIgPSBmaW5hbF9rbWVhbnMxJGNsdXN0ZXIpCmdvb2RyZWFkc19rbWVhbnMxX3Jlc3VsdApgYGAKCk5vdywgd2Ugd2lsbCB0cnkgdG8gYXBwbHkgSy1tZWFucyBhbGdvcml0aG0gZm9yIGFsbCBvZiB0aGUgbnVtZXJpY2FsIHZhcmlhYmxlcyBvZiB0aGlzIGRhdGFzZXQuCgpgYGB7cn0KZ29vZHJlYWRzX2ttZWFuczIgPC0gZ29vZHJlYWRzWyxjKCdhdmVyYWdlX3JhdGluZycsJ251bV9wYWdlcycsJ3JhdGluZ3NfY291bnQnLCd0ZXh0X3Jldmlld3NfY291bnQnKV0KCnNldC5zZWVkKDEyMykKCmZ2aXpfbmJjbHVzdChnb29kcmVhZHNfa21lYW5zMiwga21lYW5zLCBtZXRob2QgPSAid3NzIikKCmZ2aXpfbmJjbHVzdChnb29kcmVhZHNfa21lYW5zMiwga21lYW5zLCBtZXRob2QgPSAic2lsaG91ZXR0ZSIpCgpgYGAKCkFmdGVyIGNvbnNpZGVyaW5nIHRoZSBlbGJvdyBwbG90IGFuZCBzaWhvdWV0dGUgcGxvdCwgd2Ugb25jZSBhZ2FpbiBjb21lIHVwIHdpdGggdGhlIG9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzIG9mIDUuIEFzIGEgcmVzdWx0LCB3ZSB3aWxsIGFnYWluIGNsdXN0ZXIgb3VyIGRhdGFzZXQgaW50byA1IGdyb3Vwcy4KCmBgYHtyfQpzZXQuc2VlZCgxMjMpCmZpbmFsX2ttZWFuczIgPC0ga21lYW5zKGdvb2RyZWFkc19rbWVhbnMyLCA1LCBuc3RhcnQgPSAyNSkKcHJpbnQoZmluYWxfa21lYW5zMikKCmZ2aXpfY2x1c3RlcihmaW5hbF9rbWVhbnMyLCBkYXRhID0gZ29vZHJlYWRzX2ttZWFuczIpCmBgYAoKU3VycHJpc2luZyBlbm91Z2gsIHdlIHJlY2VpdmUgdGhlIGV4YWN0bHkgc2FtZSByZXN1bHQgYXMgd2UgZG8gd2l0aCBvbmx5IHR3byB2YXJpYWJsZXMsIHdpdGggZXZlcnkgb2JzZXJ2YXRpb24gYmVpbmcgaW4gdGhlIHNhbWUgZ3JvdXAgYXMgdGhlIGZpcnN0IG1vZGVsLgoKI1dlYiBzY3JhcHBpbmcgdG8gaW5zZXJ0IHllYXIgZGF0YSBmb3IgYm9va3Mvc2VyaWVzCgpBcyB3ZSBoYXZlIGhhcyBJU0JOcyBhc3NvY2lhdGVkIHdpdGggYm9va3MsIHdlIGNhbiByZXRyaWV2ZSB0aGUgeWVhciB3aGVuIHRoZSBib29rIHdhcyB3cml0dGVuIGJ5IHdlYi1zY3JhcHBpbmcgZnJvbSB0aGUgd2Vic2l0ZSBcdXJse2h0dHBzOi8vaXNibmRiLmNvbX0gYXMgZm9sbG93LiAKCmBgYHtyLCBldmFsPUYsIGVjaG89VH0KZ29vZHJlYWRzX3llYXIgPC0gZ29vZHJlYWRzCmdvb2RyZWFkc195ZWFyJFllYXIgPC0gMApmb3IgKGkgaW4gYygxOjEwMCkpIHsKICB1cmxfaSA8LSBwYXN0ZSgnaHR0cHM6Ly9pc2JuZGIuY29tL2Jvb2svJyxnb29kcmVhZHNbaSwnaXNibiddLHNlcCA9ICIiKQogIHdlYnBhZ2VfaSA8LSByZWFkX2h0bWwodXJsX2kpCiAgeWVhcl9kYXRhX2h0bWwgPC0gaHRtbF9ub2Rlcyh3ZWJwYWdlX2ksJ3RkJykKICB5ZWFyX2RhdGEgPC0gaHRtbF90ZXh0KHllYXJfZGF0YV9odG1sKQogIGdvb2RyZWFkc195ZWFyW2ksJ1llYXInXSA8LSBhcy5udW1lcmljKHllYXJfZGF0YVs2XSkKfQpnb29kcmVhZHNfeWVhciA8LSBnb29kcmVhZHNbZ29vZHJlYWRzJFllYXIgPj0gMTkwMCxdCmdvb2RyZWFkc195ZWFyIDwtIG5hLm9taXQoZ29vZHJlYWRzX3llYXIpCmdvb2RyZWFkc195ZWFyCmBgYAoKSG93ZXZlciwgdGhpcyBjb2RlIGluIFIgbWF5IHRha2UgYSBsaXR0bGUgd2hpbGUgdG8gY29kZS4gQXMgYSByZXN1bHQsIHdlIHdpbGwgcHJvY2VlZCB3aXRoIGEgc2NyaXB0IGluIFB5dGhvbiwgdGFrZWFuIGZyb20gdGhlICJHb29kcmVhZHM6IEFuYWx5c2lzIGFuZCBSZWNvbW1lbmRpbmcgQm9va3MiIG5vdGVib29rIG9uIEthZ2dsZS4KCldlIGNhbiBlYXNpbHkgc2VlIHNvbWUgTkFzIGFwcGVhcmluZyBpbiB0aGUgY29sdW1uICJZZWFyIi4gVGhpcyBpcyBkdWUgdG8gdGhlIGZhY3QgdGhhdCB0aGUgd2Vic2l0ZSB3ZSBhcmUgc2NyYXBwaW5nIGRhdGEgZnJvbSBsYWNrcyB0aGUgaW5mb3JtYXRpb24gInllYXIiLiBJbiBvcmRlciB0byBmdXJ0aGVyIGFuYWx5emUgdGhpcyBkYXRhc2V0IHdpdGggdGhlIHZhcmlhYmxlICJZZWFyIiwgd2Ugb21pdCBhbGwgb2YgTkFzLiAgCgpIb3dldmVyLCBpbnN0ZWFkIG9mIGRvaW5nIHdlYi1zY3JhcHBpbmcgd2l0aCBSLCB3ZSBkZWNpZGVkIHRvIHVzZSBhIG11Y2ggbW9yZSBlZmZlY3RpdmUgdG9vbCwgUHl0aG9uLiBUaGUgUHl0aG9uIGNvZGUgZm9yIHdlYnNjcmFwcGluZyBjYW4gYmUgZm91bmQgd2l0aGluIHRoZSBzYW1lIGZpbGUgb2YgdGhpcyBub3RlYm9vay4gQWZ0ZXIgcnVubmluZyB0aGUgUHl0aG9uIHNjcmlwdCwgd2Ugc2F2ZSB0aGUgY3N2IGZpbGUgYXMgZ29vZHJlYWRzX3llYXIuY3N2LCB3aXRoIGVuY29kaW5nICJVVEYtMTYiLiBXZSBpbXBvcnQgdGhlIGZpbGUgYXMgZm9sbG93LgogIApgYGB7cn0KZ29vZHJlYWRzX3llYXIgPC0gcmVhZC5jc3YoJy9Vc2Vycy9uZ29ob2FuZ2FuaC9EZXNrdG9wL0dvb2RyZWFkcyBLYWdnbGUgcHJvamVjdC9nb29kcmVhZHNfeWVhci5jc3YnLCBzdHJpbmdzQXNGYWN0b3IgPSBGQUxTRSwgZmlsZUVuY29kaW5nID0gIlVURi0xNiIpCnN0cihnb29kcmVhZHNfeWVhcikKcHJpbnQocGFzdGUoJ1RoZXJlIGFyZScsbnJvdyhnb29kcmVhZHNfeWVhciksJ2Jvb2tzIHdob3NlIGBZZWFyYCBhdHRyaWJ1dGUgY2FuIGJlIHJldHJpZXZlZCwgd2hpY2ggYWNjb3VudHMgZm9yJyxyb3VuZChucm93KGdvb2RyZWFkc195ZWFyKS9ucm93KGdvb2RyZWFkcykqMTAwLCBkaWdpdHMgPSAyKSwnZXByY2VudCBvZiB0aGUgb3JpZ2luYWwgZGF0YXNldC4nKSkKYGBgCgpBZnRlciByZXRyaWV2aW5nIHRoZSBZZWFyIHZhcmlhYmxlLCB3ZSB3aWxsIHByb2NlZWQgdG8gYW5hbHlzZSB0aGUgbmV3IGRhdGFzZXQgd2l0aCB0aGUgZm9sbG93aW5nIGdyYXBoczoKCiAgKiBBdmVyYWdlIG9mIGF2ZXJhZ2VfcmF0aW5nIGZvciBlYWNoIHllYXIKICAqIFRvdGFsIG51bWJlciBvZiBib29rcyBmb3IgZWFjaCB5ZWFyCiAgKiBUb3RhbCByYXRpbmdzIGNvdW50IGFuZCBhdmVyYWdlIHJhdGluZ3MgY291bnQgZm9yIGVhY2ggYm9vayBvZiBlYWNoIHllYXIKICAqIFRvdGFsIHRleHQgcmV2aWV3cyBjb3VudCBhbmQgYXZlcmFnZSB0ZXh0IHJldmlld3MgY291bnQgb2YgZWFjaCBib29rIGZvciBlYWNoIHllYXIKICAKIyNBdmVyYWdlIG9mIGF2ZXJhZ2VfcmF0aW5nIG9mIGFsbCBib29rcyBmb3IgZWFjaCB5ZWFyCgpgYGB7cn0KbWVhbl95ZWFyIDwtIGFnZ3JlZ2F0ZShhdmVyYWdlX3JhdGluZyB+IFllYXIsIGRhdGEgPSBnb29kcmVhZHNfeWVhciwgRlVOID0gbWVhbikKbWVhbl95ZWFyCgpnZ3Bsb3QoZGF0YSA9IG1lYW5feWVhciwgYWVzKHggPSBZZWFyLCB5ID0gYXZlcmFnZV9yYXRpbmcpKSArCiAgZ2VvbV9saW5lKGNvbG9yID0gIiNGQzRFMDciLCBzaXplID0gMSkgKwogIGxhYnMoeCA9ICJZZWFyIiwgeSA9ICJBdmVyYWdlIHJhdGluZyIsIHRpdGxlID0gIkF2ZXJhZ2UgcmF0aW5nIGZvciBlYWNoIHllYXIiKSArIAogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpCgpnb29kcmVhZHNfeWVhcltnb29kcmVhZHNfeWVhciRZZWFyID09IDE5MjIsXQpnb29kcmVhZHNfeWVhcltnb29kcmVhZHNfeWVhciRZZWFyID09IDE5MzEsXQpgYGAKCkZyb20gdGhlIGdyYXBoLCB3ZSBjYW4gc2VlIHR3byBvcHBvc2l0ZSBwZWFrcyBhcHBlYXJpbmc6CgogICogTWF4aW11bSBwZWFrOiBUaGUgYXZlcmFnZSByYXRpbmdzIG9mIGJvb2tzIHdyaXR0ZW4gaW4gMTkyMiByZWFjaGVkIHRoZSBwZWFrIG9mIDUuMC4gQWZ0ZXIgcmUtY2hlY2tpbmcgd2l0aCB0aGUgZGF0YSByZXRyaWV2ZWQsIHRoZXJlIGlzIG9ubHkgb25lIGJvb2sgd3JpdHRlbiBpbiAxOTIyLiBNb3Jlb3ZlciwgdGhpcyBib29rIHJlY2VpdmVkIDAgcmF0aW5nIGFuZCAwIHRleHQgcmV2aWV3LiAKICAqIE1pbmltdW0gcGVhazogVGhlIGF2ZXJhZ2UgcmF0aW5ncyBvZiBib29rcyByZWFjaGVkIGl0cyBsb3dlc3QgaW4gMTkzMSB3aXRoIHRoZSBhdmVyYWdlIG9mIDIuNzUsIHRoZW4gZ3JhZHVhbGx5IHJlY292ZXIgdW50aWwgMTk0MC4gQXMgZXhwZWN0ZWQsIGluIDE5MzEsIHRoZXJlIGlzIG9ubHkgb25lIGJvb2sgd3JpdHRlbiwgd2l0aCA0IHJhdGluZ3MgYW5kIDAgdGV4dCByZXZpZXcuCiAgCkFwYXJ0IGZyb20gdGhlIHBlYWtzIG1lbnRpb25lZCBhYm92ZSwgdGhlcmUgaXMgbm8gb3RoZXIgYWJub3JtYWwgcG9pbnRzIHdpdGhpbiB0aGUgZ3JhcGguIEZvciB0aGUgcmVtYWluaW5nIHllYXJzLCB0aGUgYXZlcmFnZSByYXRpbmcgZmx1Y3R1YXRlIGFyb3VuZCA0LjAuCgojI1RvdGFsIGJvb2tzIG9mIGVhY2ggeWVhcgoKYGBge3J9CnRvdGFsX2Jvb2tzX3llYXIgPC0gZ29vZHJlYWRzX3llYXIgJT4lIGNvdW50KFllYXIpCm5hbWVzKHRvdGFsX2Jvb2tzX3llYXIpIDwtIGMoJ1llYXInLCdudW1iZXJfb2ZfYm9va3MnKQp0b3RhbF9ib29rc195ZWFyCgpnZ3Bsb3QoZGF0YSA9IHRvdGFsX2Jvb2tzX3llYXIsIGFlcyh4ID0gWWVhciwgeSA9IG51bWJlcl9vZl9ib29rcykpICsgCiAgZ2VvbV9saW5lKGNvbG9yID0gIiMwMEFGQkIiLCBzaXplID0gMSkgKwogIGxhYnMoeCA9ICJZZWFyIiwgeSA9ICJUb3RhbCBudW1iZXIgb2YgYm9va3MiLCB0aXRsZSA9ICJOdW1iZXIgb2YgYm9va3Mgd3JpdHRlbiBmb3IgZWFjaCB5ZWFyIikgKyAKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKQpgYGAKCldlIGNhbiBlYXNpbHkgc2VlIHRoYXQsIGJlZm9yZSAxOTc1LCB0aGVyZSBhcmUgbm90IG1hbnkgYm9va3Mgd3JpdHRlbiBpbiBlYWNoIHllYXIuIEhvd2V2ZXIsIGFmdGVyIHRoYXQgdGltZXN0YW1wLCB0aGUgYm9va3MgcHVibGlzaGVkIGV2ZXJ5IHllYXIgc3RhcnQgdG8gaW5jcmVhc2UgZ3JhZHVhbGx5LCBhbmQgc2t5cm9ja2V0dGVkIGJldHdlZW4gMTk5MCBhbmQgMjAwNi4gQWZ0ZXIgcmVhY2hpbmcgaXRzIHBlYWssIHRoZSBudW1iZXIgb2YgYm9va3Mgc3VkZGVubHkgZHJvcHBlZCBzaWduaWZpY2FudGx5LCBhbmQgc2luY2UgMjAxNiwgdGhlcmUgYXJlIGxlc3MgdGhhbiAxMCBuZXcgYm9va3Mgd3JpdHRlbiBlYWNoIHllYXIuCgojVG90YWwgcmF0aW5ncyBjb3VudCBhbmQgYXZlcmFnZSByYXRpbmdzIGNvdW50IGZvciBlYWNoIGJvb2sgb2YgZWFjaCB5ZWFyCgpgYGB7cn0Kc3VtX3JhdGluZ3NfY291bnRfeWVhciA8LSBhZ2dyZWdhdGUocmF0aW5nc19jb3VudCB+IFllYXIsIGRhdGEgPSBnb29kcmVhZHNfeWVhciwgRlVOID0gc3VtKQpzdW1fcmF0aW5nc19jb3VudF95ZWFyCgpnZ3Bsb3QoZGF0YSA9IHN1bV9yYXRpbmdzX2NvdW50X3llYXIsIGFlcyh4ID0gWWVhciwgeSA9IHJhdGluZ3NfY291bnQpKSArCiAgZ2VvbV9saW5lKGNvbG9yID0gInB1cnBsZSIsIHNpemUgPSAxKSArCiAgbGFicyh4ID0gIlllYXIiLCB5ID0gIlRvdGFsIHJhdGluZ3MgY291bnQiLCB0aXRsZSA9ICJUb3RhbCByYXRpbmdzIGNvdW50IG9mIGFsbCBib29rcyBmb3IgZWFjaCB5ZWFyIikgKyAKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKQoKYXZlcmFnZV9yYXRpbmdzX2NvdW50X3llYXIgPC0gYWdncmVnYXRlKHJhdGluZ3NfY291bnQgfiBZZWFyLCBkYXRhID0gZ29vZHJlYWRzX3llYXIsIEZVTiA9IG1lYW4pCmF2ZXJhZ2VfcmF0aW5nc19jb3VudF95ZWFyCgpnZ3Bsb3QoZGF0YSA9IGF2ZXJhZ2VfcmF0aW5nc19jb3VudF95ZWFyLCBhZXMoeCA9IFllYXIsIHkgPSByYXRpbmdzX2NvdW50KSkgKwogIGdlb21fbGluZShjb2xvciA9ICJwdXJwbGUiLCBzaXplID0gMSkgKwogIGxhYnMoeCA9ICJZZWFyIiwgeSA9ICJBdmVyYWdlIHJhdGluZ3MgY291bnQiLCB0aXRsZSA9ICJBdmVyYWdlIHJhdGluZ3MgY291bnQgb2YgZWFjaCBib29rIGZvciBlYWNoIHllYXIiKSArIAogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpCmBgYAoKVGhlIHRvdGFsIG51bWJlciBvZiByYXRpbmdzIGVhY2ggeWVhciBzZWVtcyB0byBmb2xsb3cgdGhlIHBhdHRlcm4gb2YgdGhlIG51bWJlciBvZiBib29rcyBwdWJsaXNoZWQgZWFjaCB5ZWFyLiBIb3dldmVyLCB0aGUgYXZlcmFnZSByYXRpbmdzIGNvdW50IGZvciBlYWNoIGJvb2sgZWFjaCB5ZWFyIGRvZXMgbm90LiBXZSBjYW4gc2VlIHR3byBncmVhdGVzdCBwZWFrcyBvZiBhcm91bmQgNjAsMDAwIHJhdGluZ3MgaW4gMTk1MiBhbmQgMjAxOSwgYWxvbmcgd2l0aCB2YXJpb3VzIG90aGVyIHNtYWxsZXIgcGVha3MuIFRoZSBhdmVyYWdlIGRvZXMgbm90IHNlZW0gdG8gZm9sbG93IGFueSBwYXR0ZXJuLgoKI1RvdGFsIHRleHQgcmV2aWV3cyBjb3VudCBhbmQgYXZlcmFnZSB0ZXh0IHJldmlld3MgY291bnQgZm9yIGVhY2ggYm9vayBvZiBlYWNoIHllYXIKCmBgYHtyfQpzdW1fdGV4dF9yZXZpZXdzX2NvdW50X3llYXIgPC0gYWdncmVnYXRlKHRleHRfcmV2aWV3c19jb3VudCB+IFllYXIsIGRhdGEgPSBnb29kcmVhZHNfeWVhciwgRlVOID0gc3VtKQpzdW1fdGV4dF9yZXZpZXdzX2NvdW50X3llYXIKCmdncGxvdChkYXRhID0gc3VtX3RleHRfcmV2aWV3c19jb3VudF95ZWFyLCBhZXMoeCA9IFllYXIsIHkgPSB0ZXh0X3Jldmlld3NfY291bnQpKSArCiAgZ2VvbV9saW5lKGNvbG9yID0gIiM5QUNEMzIiLCBzaXplID0gMSkgKwogIGxhYnMoeCA9ICJZZWFyIiwgeSA9ICJUb3RhbCByYXRpbmdzIGNvdW50IiwgdGl0bGUgPSAiVG90YWwgdGV4dCByZXZpZXdzIGNvdW50IG9mIGFsbCBib29rcyBmb3IgZWFjaCB5ZWFyIikgKyAKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKQoKYXZlcmFnZV90ZXh0X3Jldmlld3NfY291bnRfeWVhciA8LSBhZ2dyZWdhdGUodGV4dF9yZXZpZXdzX2NvdW50IH4gWWVhciwgZGF0YSA9IGdvb2RyZWFkc195ZWFyLCBGVU4gPSBtZWFuKQphdmVyYWdlX3RleHRfcmV2aWV3c19jb3VudF95ZWFyCgpnZ3Bsb3QoZGF0YSA9IGF2ZXJhZ2VfdGV4dF9yZXZpZXdzX2NvdW50X3llYXIsIGFlcyh4ID0gWWVhciwgeSA9IHRleHRfcmV2aWV3c19jb3VudCkpICsKICBnZW9tX2xpbmUoY29sb3IgPSAiIzlBQ0QzMiIsIHNpemUgPSAxKSArCiAgbGFicyh4ID0gIlllYXIiLCB5ID0gIkF2ZXJhZ2UgdGV4dCByZXZpZXdzIGNvdW50IiwgdGl0bGUgPSAiQXZlcmFnZSB0ZXh0IHJldmlld3MgY291bnQgb2YgZWFjaCBib29rIGZvciBlYWNoIHllYXIiKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikKYGBgCgpPbmNlIGFnYWluLCB0aGUgdG90YWwgbnVtYmVyIG9mIHRleHQgcmV2aWV3cyBjb3VudCBlYWNoIHllYXIgc2VlbXMgdG8gZm9sbG93IHRoZSBwYXR0ZXJuIG9mIHRoZSBudW1iZXIgb2YgYm9va3Mgd3JpdHRlbiBlYWNoIHllYXIuIFRoZSBhdmVyYWdlIHJldmlld3MgY291bnQgZm9yIGVhY2ggYm9vayBlYWNoIHllYXIgcmVhY2hlcyBpdCBwZWFrIGluIDIwMTksIGFsc28gYWxvbmcgd2l0aCBzb21lIG90aGVyIHNtYWxsZXIgcGVha3MuIFRoaXMgdGltZSwgd2UgY2FuIG9ic2VydmUgdGhhdCB0aGVyZSBpcyBhbiBvdmVyYWxsIGluY3JlYXNpbmcgdHJlbmQgd2l0aCByZXNwZWN0IHRvIHRpbWUsIHdoaWNoIG1lYW5zIHRoYXQgYXMgdGltZSBnb2VzLCBwZW9wbGUgYXJlIGxlYXZpbmcgbW9yZSBhbmQgbW9yZSB0ZXh0IHJldmlld3Mu